[{"data":1,"prerenderedAt":5315},["ShallowReactive",2],{"search-sections-aperture":3,"nav-aperture":1072,"content-tree-aperture":1127,"footer-resources":1152,"content-/v1.0.3/learn/quickstart":3842,"surround-/v1.0.3/learn/quickstart":5312},[4,10,14,20,25,30,35,41,46,51,56,59,64,69,74,79,84,89,94,97,102,107,112,117,122,127,131,136,141,146,151,156,161,166,170,174,177,182,187,191,196,201,206,211,215,219,224,229,234,239,244,248,253,258,263,268,273,277,282,286,291,296,301,306,311,316,321,326,331,336,340,345,350,355,360,365,369,374,379,384,389,394,399,404,409,413,417,422,427,432,437,442,447,452,457,461,466,471,475,480,484,489,494,499,504,509,513,518,523,528,532,536,541,546,551,556,560,565,570,575,580,585,589,593,597,601,605,610,615,619,623,628,633,637,642,647,652,657,662,667,672,677,682,687,692,696,701,705,710,715,719,723,727,731,736,741,745,749,753,758,763,768,772,775,779,783,787,792,795,798,803,807,813,818,823,828,833,838,842,846,850,855,860,865,869,874,879,884,889,894,899,904,908,912,915,920,924,928,932,937,942,946,951,956,961,966,971,975,978,983,988,993,998,1003,1008,1011,1016,1021,1026,1031,1035,1039,1043,1048,1053,1057,1062,1067],{"id":5,"title":6,"titles":7,"content":8,"level":9},"/v1.0.3/overview","Overview",[],"Config-driven capitan-to-OTEL bridge for Go applications",1,{"id":11,"title":6,"titles":12,"content":13,"level":9},"/v1.0.3/overview#overview",[],"Connecting application events to observability platforms usually means manual instrumentation scattered throughout your code. Aperture offers a declarative alternative: configure how capitan events become OpenTelemetry signals, and the bridge handles the rest. // Define signals and keys\norderCreated := capitan.NewSignal(\"order.created\", \"Order created\")\norderID := capitan.NewStringKey(\"order_id\")\n\n// Create aperture with your OTEL providers\nap, _ := aperture.New(cap, logProvider, meterProvider, traceProvider)\ndefer ap.Close()\n\n// Configure the bridge\nschema := aperture.Schema{\n    Metrics: []aperture.MetricSchema{\n        {Signal: \"order.created\", Name: \"orders_created_total\", Type: \"counter\"},\n    },\n}\nap.Apply(schema)\n\n// Events automatically become OTEL signals\ncap.Emit(ctx, orderCreated, orderID.Field(\"ORDER-123\"))\n// ^ Logged to OTEL + counter incremented Schema-driven, hot-reloadable, explicit provider configuration.",{"id":15,"title":16,"titles":17,"content":18,"level":19},"/v1.0.3/overview#architecture","Architecture",[6],"┌─────────────────────────────────────────────────────────────┐\n│                         Aperture                            │\n│                                                             │\n│  ┌─────────────────────────────────────────────────────┐   │\n│  │                   Observer                           │   │\n│  │         (receives all capitan events)               │   │\n│  └─────────────────────────────────────────────────────┘   │\n│                           │                                 │\n│           ┌───────────────┼───────────────┐                 │\n│           ▼               ▼               ▼                 │\n│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐           │\n│  │   Logger    │ │   Metrics   │ │   Traces    │           │\n│  │  Transform  │ │  Transform  │ │  Transform  │           │\n│  └─────────────┘ └─────────────┘ └─────────────┘           │\n│           │               │               │                 │\n│           ▼               ▼               ▼                 │\n│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐           │\n│  │    OTEL     │ │    OTEL     │ │    OTEL     │           │\n│  │  LogProvider│ │MeterProvider│ │TraceProvider│           │\n│  └─────────────┘ └─────────────┘ └─────────────┘           │\n└─────────────────────────────────────────────────────────────┘ Aperture registers as a capitan observer, receives all events, and transforms them according to configuration. You provide pre-configured OTEL providers; aperture handles the event-to-signal mapping.",2,{"id":21,"title":22,"titles":23,"content":24,"level":19},"/v1.0.3/overview#philosophy","Philosophy",[6],"Aperture draws a clear line: opinionated about event transformation, agnostic about provider configuration. What aperture decides: How capitan fields become OTEL attributesHow signal pairs correlate into spansHow events filter to logs What you decide: Which exporters to use (OTLP, stdout, custom)How to batch and bufferSecurity, sampling, resource attributes // Your configuration choices\nlogProvider := log.NewLoggerProvider(\n    log.WithResource(myResource),\n    log.WithProcessor(log.NewBatchProcessor(myExporter)),\n)\n\n// Aperture's transformation rules\nschema := aperture.Schema{\n    Metrics: []aperture.MetricSchema{...},\n    Traces:  []aperture.TraceSchema{...},\n    Logs:    &aperture.LogSchema{...},\n}\n\n// Two concerns, cleanly separated\nap, _ := aperture.New(cap, logProvider, meterProvider, traceProvider)\nap.Apply(schema)",{"id":26,"title":27,"titles":28,"content":29,"level":19},"/v1.0.3/overview#capabilities","Capabilities",[6],"Configuration drives behavior: Metrics - Count events, record values, track distributions. Signal emissions become counter increments, gauge updates, or histogram observations. Traces - Correlate signal pairs into spans. request.started and request.completed with matching request IDs create a single span. Logs - Filter events to logs. Whitelist specific signals or log everything. Context - Extract values from context.Context and add them as attributes to all three signal types. Schema - Load configuration from YAML/JSON files with hot-reload support.",{"id":31,"title":32,"titles":33,"content":34,"level":19},"/v1.0.3/overview#priorities","Priorities",[6],"",{"id":36,"title":37,"titles":38,"content":39,"level":40},"/v1.0.3/overview#explicit-configuration","Explicit Configuration",[6,32],"No magic defaults that might expose data unexpectedly. You construct providers explicitly, you configure transformations explicitly. // You control the exporter\nexporter, _ := otlptracehttp.New(ctx, otlptracehttp.WithEndpoint(\"localhost:4318\"))\n\n// You control the provider\ntraceProvider := trace.NewTracerProvider(\n    trace.WithSpanProcessor(trace.NewBatchSpanProcessor(exporter)),\n    trace.WithSampler(trace.TraceIDRatioBased(0.1)),\n)\n\n// You pass it to aperture\nap, _ := aperture.New(cap, logProvider, meterProvider, traceProvider)",3,{"id":42,"title":43,"titles":44,"content":45,"level":40},"/v1.0.3/overview#automatic-type-handling","Automatic Type Handling",[6,32],"Field transformation preserves types. Built-in types convert directly to OTEL attributes; custom types are automatically JSON serialized. type OrderInfo struct {\n    ID     string  `json:\"id\"`\n    Total  float64 `json:\"total\"`\n    Secret string  `json:\"-\"` // Excluded from JSON\n}\n\norderKey := capitan.NewKey[OrderInfo](\"order\", \"Order details\")\n\ncap.Emit(ctx, orderCreated, orderKey.Field(OrderInfo{\n    ID:     \"ORD-123\",\n    Total:  99.99,\n    Secret: \"internal\",\n}))\n// OrderInfo serialized as JSON: {\"id\":\"ORD-123\",\"total\":99.99} Use standard JSON struct tags to control what gets exported.",{"id":47,"title":48,"titles":49,"content":50,"level":40},"/v1.0.3/overview#otel-native","OTEL Native",[6,32],"Aperture exposes standard OTEL interfaces. Use them directly when you need to: logger := ap.Logger(\"orders\")\nmeter := ap.Meter(\"orders\")\ntracer := ap.Tracer(\"orders\")\n\n// Standard OTEL usage\nctx, span := tracer.Start(ctx, \"process-order\")\ndefer span.End() The bridge adds automatic event transformation; it doesn't replace direct OTEL usage. html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}",{"id":52,"title":53,"titles":54,"content":55,"level":9},"/v1.0.3/learn/quickstart","Quickstart",[],"Get aperture running in five minutes",{"id":57,"title":53,"titles":58,"content":34,"level":9},"/v1.0.3/learn/quickstart#quickstart",[],{"id":60,"title":61,"titles":62,"content":63,"level":19},"/v1.0.3/learn/quickstart#installation","Installation",[53],"go get github.com/zoobz-io/aperture Requires Go 1.24+ and capitan.",{"id":65,"title":66,"titles":67,"content":68,"level":19},"/v1.0.3/learn/quickstart#minimal-setup","Minimal Setup",[53],"package main\n\nimport (\n    \"context\"\n    \"time\"\n\n    \"github.com/zoobz-io/aperture\"\n    \"github.com/zoobz-io/capitan\"\n    \"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp\"\n    \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp\"\n    \"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp\"\n    \"go.opentelemetry.io/otel/sdk/log\"\n    \"go.opentelemetry.io/otel/sdk/metric\"\n    \"go.opentelemetry.io/otel/sdk/trace\"\n)\n\nfunc main() {\n    ctx := context.Background()\n\n    // 1. Create OTEL providers\n    logExporter, _ := otlploghttp.New(ctx, otlploghttp.WithEndpoint(\"localhost:4318\"), otlploghttp.WithInsecure())\n    logProvider := log.NewLoggerProvider(log.WithProcessor(log.NewBatchProcessor(logExporter)))\n    defer logProvider.Shutdown(ctx)\n\n    metricExporter, _ := otlpmetrichttp.New(ctx, otlpmetrichttp.WithEndpoint(\"localhost:4318\"), otlpmetrichttp.WithInsecure())\n    meterProvider := metric.NewMeterProvider(metric.WithReader(\n        metric.NewPeriodicReader(metricExporter, metric.WithInterval(60*time.Second)),\n    ))\n    defer meterProvider.Shutdown(ctx)\n\n    traceExporter, _ := otlptracehttp.New(ctx, otlptracehttp.WithEndpoint(\"localhost:4318\"), otlptracehttp.WithInsecure())\n    traceProvider := trace.NewTracerProvider(trace.WithSpanProcessor(trace.NewBatchSpanProcessor(traceExporter)))\n    defer traceProvider.Shutdown(ctx)\n\n    // 2. Create aperture (no config = log all events)\n    cap := capitan.Default()\n    ap, err := aperture.New(cap, logProvider, meterProvider, traceProvider)\n    if err != nil {\n        panic(err)\n    }\n    defer ap.Close()\n\n    // 3. Emit events - they automatically become OTEL logs\n    sig := capitan.NewSignal(\"app.started\", \"Application started\")\n    cap.Emit(ctx, sig)\n\n    // 4. Graceful shutdown\n    cap.Shutdown()\n}",{"id":70,"title":71,"titles":72,"content":73,"level":19},"/v1.0.3/learn/quickstart#whats-happening","What's Happening",[53],"OTEL providers - You configure how telemetry reaches your backend (exporters, batching, endpoints)Aperture bridge - Registers as a capitan observer, receives all eventsAutomatic transformation - Events become OTEL logs with signal name and fields as attributesProvider shutdown - Your responsibility to flush and close providers The key insight: aperture handles event-to-signal transformation, you handle OTEL configuration.",{"id":75,"title":76,"titles":77,"content":78,"level":19},"/v1.0.3/learn/quickstart#with-configuration","With Configuration",[53],"Add metrics and trace correlation: // Define signals\norderCreated := capitan.NewSignal(\"order.created\", \"Order created\")\norderCompleted := capitan.NewSignal(\"order.completed\", \"Order completed\")\norderID := capitan.NewStringKey(\"order_id\")\ntotal := capitan.NewFloat64Key(\"total\")\n\n// Create aperture\nap, _ := aperture.New(cap, logProvider, meterProvider, traceProvider)\ndefer ap.Close()\n\n// Configure transformations with schema\nschema := aperture.Schema{\n    // Count order creations\n    Metrics: []aperture.MetricSchema{\n        {\n            Signal: \"order.created\",\n            Name:   \"orders_created_total\",\n            Type:   \"counter\",\n        },\n    },\n\n    // Correlate order start/end into spans\n    Traces: []aperture.TraceSchema{\n        {\n            Start:          \"order.created\",\n            End:            \"order.completed\",\n            CorrelationKey: \"order_id\",\n            SpanName:       \"order_processing\",\n        },\n    },\n\n    // Only log order events\n    Logs: &aperture.LogSchema{\n        Whitelist: []string{\"order.created\", \"order.completed\"},\n    },\n}\nap.Apply(schema)\n\n// Emit order flow\ncap.Emit(ctx, orderCreated, orderID.Field(\"ORD-123\"), total.Field(99.99))\n// ^ Counter incremented, log recorded, span started\n\ntime.Sleep(100 * time.Millisecond)\n\ncap.Emit(ctx, orderCompleted, orderID.Field(\"ORD-123\"))\n// ^ Log recorded, span completed",{"id":80,"title":81,"titles":82,"content":83,"level":19},"/v1.0.3/learn/quickstart#using-otel-directly","Using OTEL Directly",[53],"Aperture exposes standard OTEL interfaces: // Get OTEL primitives\nlogger := ap.Logger(\"orders\")\nmeter := ap.Meter(\"orders\")\ntracer := ap.Tracer(\"orders\")\n\n// Create custom metrics\ncounter, _ := meter.Int64Counter(\"custom_counter\")\ncounter.Add(ctx, 1)\n\n// Create custom spans\nctx, span := tracer.Start(ctx, \"custom-operation\")\ndefer span.End()",{"id":85,"title":86,"titles":87,"content":88,"level":19},"/v1.0.3/learn/quickstart#next-steps","Next Steps",[53],"Concepts - Understand the core modelMetrics Guide - Configure metric transformationsTraces Guide - Set up trace correlationTesting Guide - Test your aperture configuration html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}",{"id":90,"title":91,"titles":92,"content":93,"level":9},"/v1.0.3/learn/concepts","Concepts",[],"Core concepts and mental model",{"id":95,"title":91,"titles":96,"content":34,"level":9},"/v1.0.3/learn/concepts#concepts",[],{"id":98,"title":99,"titles":100,"content":101,"level":19},"/v1.0.3/learn/concepts#the-bridge-model","The Bridge Model",[91],"Aperture bridges two systems: Capitan - In-process event coordination. Signals, fields, observers. OpenTelemetry - Vendor-neutral observability. Logs, metrics, traces. ┌─────────────────┐          ┌─────────────────┐\n│     Capitan     │          │  OpenTelemetry  │\n│                 │          │                 │\n│  Signals        │──────────│  Logs           │\n│  Fields         │  Aperture│  Metrics        │\n│  Events         │──────────│  Traces         │\n│                 │          │                 │\n└─────────────────┘          └─────────────────┘",{"id":103,"title":104,"titles":105,"content":106,"level":19},"/v1.0.3/learn/concepts#configuration-vs-providers","Configuration vs Providers",[91],"Two separate concerns:",{"id":108,"title":109,"titles":110,"content":111,"level":40},"/v1.0.3/learn/concepts#provider-configuration-your-responsibility","Provider Configuration (Your Responsibility)",[91,104],"How OTEL talks to backends: // Exporter: where data goes\nexporter, _ := otlptracehttp.New(ctx, otlptracehttp.WithEndpoint(\"jaeger:4318\"))\n\n// Provider: how it's processed\ntraceProvider := trace.NewTracerProvider(\n    trace.WithSpanProcessor(trace.NewBatchSpanProcessor(exporter)),\n    trace.WithSampler(trace.TraceIDRatioBased(0.1)),\n    trace.WithResource(myResource),\n)",{"id":113,"title":114,"titles":115,"content":116,"level":40},"/v1.0.3/learn/concepts#schema-configuration-transformation-rules","Schema Configuration (Transformation Rules)",[91,104],"How events become signals: schema := aperture.Schema{\n    Metrics: []aperture.MetricSchema{...},\n    Traces:  []aperture.TraceSchema{...},\n    Logs:    &aperture.LogSchema{...},\n}",{"id":118,"title":119,"titles":120,"content":121,"level":19},"/v1.0.3/learn/concepts#field-transformation","Field Transformation",[91],"Capitan fields become OTEL attributes. This happens automatically for built-in types: orderID := capitan.NewStringKey(\"order_id\")\ntotal := capitan.NewFloat64Key(\"total\")\n\ncap.Emit(ctx, sig, orderID.Field(\"ORD-123\"), total.Field(99.99)) Becomes: log.String(\"order_id\", \"ORD-123\")\nlog.Float64(\"total\", 99.99)",{"id":123,"title":124,"titles":125,"content":126,"level":40},"/v1.0.3/learn/concepts#custom-types","Custom Types",[91,119],"Custom types are automatically JSON serialized: type OrderInfo struct {\n    ID     string  `json:\"id\"`\n    Total  float64 `json:\"total\"`\n    Secret string  `json:\"-\"` // Excluded from JSON\n}\n\norderKey := capitan.NewKey[OrderInfo](\"order\", \"Order details\")\n\ncap.Emit(ctx, orderCreated, orderKey.Field(OrderInfo{\n    ID:     \"ORD-123\",\n    Total:  99.99,\n    Secret: \"internal\",\n}))\n// Becomes: log.String(\"order\", \"{\\\"id\\\":\\\"ORD-123\\\",\\\"total\\\":99.99}\") Use standard JSON struct tags to control what gets exported.",{"id":128,"title":129,"titles":130,"content":34,"level":19},"/v1.0.3/learn/concepts#signal-types","Signal Types",[91],{"id":132,"title":133,"titles":134,"content":135,"level":40},"/v1.0.3/learn/concepts#logs","Logs",[91,129],"Events become log records. By default, all events are logged. Use a whitelist to filter: schema := aperture.Schema{\n    Logs: &aperture.LogSchema{\n        Whitelist: []string{\"order.created\", \"order.failed\"}, // Only these are logged\n    },\n}",{"id":137,"title":138,"titles":139,"content":140,"level":40},"/v1.0.3/learn/concepts#metrics","Metrics",[91,129],"Signals trigger metric operations. Four instrument types: TypeUse CaseValue SourceCounterCount occurrencesEvent emissionGaugeCurrent valueField valueHistogramDistributionField valueUpDownCounterBidirectional countField value schema := aperture.Schema{\n    Metrics: []aperture.MetricSchema{\n        {Signal: \"order.created\", Name: \"orders_total\", Type: \"counter\"},\n        {Signal: \"system.cpu\", Name: \"cpu_percent\", Type: \"gauge\", ValueKey: \"percent\"},\n    },\n}",{"id":142,"title":143,"titles":144,"content":145,"level":40},"/v1.0.3/learn/concepts#traces","Traces",[91,129],"Signal pairs become spans. Start and end events correlate by a key value: schema := aperture.Schema{\n    Traces: []aperture.TraceSchema{\n        {\n            Start:          \"request.started\",\n            End:            \"request.completed\",\n            CorrelationKey: \"request_id\",\n            SpanName:       \"http_request\",\n        },\n    },\n}\n\n// Events correlate by request_id value\ncap.Emit(ctx, reqStarted, requestID.Field(\"REQ-123\"))\ncap.Emit(ctx, reqCompleted, requestID.Field(\"REQ-123\"))  // Completes span",{"id":147,"title":148,"titles":149,"content":150,"level":19},"/v1.0.3/learn/concepts#name-based-matching","Name-Based Matching",[91],"Schema configuration uses string names that match at runtime: // Define signals with names\norderCreated := capitan.NewSignal(\"order.created\", \"Order created\")\norderID := capitan.NewStringKey(\"order_id\")\n\n// Schema references by name\nschema := aperture.Schema{\n    Metrics: []aperture.MetricSchema{\n        {Signal: \"order.created\", Name: \"orders_total\", Type: \"counter\"},\n    },\n    Traces: []aperture.TraceSchema{\n        {\n            Start:          \"order.created\",\n            End:            \"order.completed\",\n            CorrelationKey: \"order_id\",\n        },\n    },\n}\n\n// At runtime, event.Signal().Name() is compared to schema signal names This enables hot-reload without recompilation.",{"id":152,"title":153,"titles":154,"content":155,"level":19},"/v1.0.3/learn/concepts#context-extraction","Context Extraction",[91],"Pull values from context.Context into attributes: type ctxKey string\nconst userIDKey ctxKey = \"user_id\"\n\n// Register the context key\nap.RegisterContextKey(\"user_id\", userIDKey)\n\n// Reference by name in schema\nschema := aperture.Schema{\n    Context: &aperture.ContextSchema{\n        Logs: []string{\"user_id\"},\n    },\n}\nap.Apply(schema)\n\nctx = context.WithValue(ctx, userIDKey, \"user-123\")\ncap.Emit(ctx, sig)\n// ^ Log includes user_id=\"user-123\" attribute",{"id":157,"title":158,"titles":159,"content":160,"level":19},"/v1.0.3/learn/concepts#schema-based-configuration","Schema-Based Configuration",[91],"Load configuration from files instead of code: # observability.yaml\nmetrics:\n  - signal: order.created\n    name: orders_total\n    type: counter\n\nlogs:\n  whitelist:\n    - order.created configBytes, _ := os.ReadFile(\"observability.yaml\")\nschema, _ := aperture.LoadSchemaFromYAML(configBytes)\nap.Apply(schema) Enables hot-reload without recompilation.",{"id":162,"title":163,"titles":164,"content":165,"level":19},"/v1.0.3/learn/concepts#lifecycle","Lifecycle",[91],"Create providers - Configure OTEL exporters and processorsCreate aperture - Pass providersApply schema - Configure transformation rulesEmit events - Capitan events automatically transformClose aperture - Stop observing (does NOT shutdown providers)Shutdown providers - Your responsibility // 1. Providers\nlogProvider := log.NewLoggerProvider(...)\ndefer logProvider.Shutdown(ctx)  // 6. Your shutdown\n\n// 2. Aperture\nap, _ := aperture.New(cap, logProvider, meterProvider, traceProvider)\ndefer ap.Close()  // 5. Stop observing\n\n// 3. Schema\nap.Apply(schema)\n\n// 4. Events\ncap.Emit(ctx, sig, fields...)",{"id":167,"title":86,"titles":168,"content":169,"level":19},"/v1.0.3/learn/concepts#next-steps",[91],"Architecture - Implementation detailsMetrics Guide - Deep dive on metricsTraces Guide - Deep dive on traces html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sfm-E, html code.shiki .sfm-E{--shiki-default:var(--shiki-variable)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}",{"id":171,"title":16,"titles":172,"content":173,"level":9},"/v1.0.3/learn/architecture",[],"Internal design and implementation details",{"id":175,"title":16,"titles":176,"content":34,"level":9},"/v1.0.3/learn/architecture#architecture",[],{"id":178,"title":179,"titles":180,"content":181,"level":19},"/v1.0.3/learn/architecture#component-overview","Component Overview",[16],"┌─────────────────────────────────────────────────────────────────┐\n│                           Aperture                              │\n│                                                                 │\n│  ┌──────────────────────────────────────────────────────────┐  │\n│  │                       Observer                            │  │\n│  │  capitan.Observe() → receives all events                 │  │\n│  └────────────────────────────┬─────────────────────────────┘  │\n│                               │                                 │\n│               ┌───────────────┼───────────────┐                 │\n│               ▼               ▼               ▼                 │\n│  ┌────────────────┐ ┌────────────────┐ ┌────────────────┐      │\n│  │  Log Handler   │ │ Metric Handler │ │ Trace Handler  │      │\n│  │                │ │                │ │                │      │\n│  │  - Whitelist   │ │  - Counter     │ │  - Correlation │      │\n│  │  - Transform   │ │  - Gauge       │ │  - Span start  │      │\n│  │  - Context     │ │  - Histogram   │ │  - Span end    │      │\n│  └────────┬───────┘ └───────┬────────┘ └───────┬────────┘      │\n│           │                 │                   │                │\n│           ▼                 ▼                   ▼                │\n│  ┌────────────────┐ ┌────────────────┐ ┌────────────────┐      │\n│  │  LogProvider   │ │ MeterProvider  │ │ TraceProvider  │      │\n│  │  (from user)   │ │  (from user)   │ │  (from user)   │      │\n│  └────────────────┘ └────────────────┘ └────────────────┘      │\n└─────────────────────────────────────────────────────────────────┘",{"id":183,"title":184,"titles":185,"content":186,"level":19},"/v1.0.3/learn/architecture#event-flow","Event Flow",[16],"Application emits capitan eventAperture's observer receives itThree handlers process in parallel:\nLog handler: filters, transforms, emitsMetric handler: increments/records metricsTrace handler: starts/ends spans based on correlation",{"id":188,"title":189,"titles":190,"content":34,"level":19},"/v1.0.3/learn/architecture#key-types","Key Types",[16],{"id":192,"title":193,"titles":194,"content":195,"level":40},"/v1.0.3/learn/architecture#aperture","Aperture",[16,189],"The main entry point. Registers as a capitan observer and holds references to providers. type Aperture struct {\n    logProvider   log.LoggerProvider\n    meterProvider metric.MeterProvider\n    traceProvider trace.TracerProvider\n    contextKeys   map[string]any  // name → context key for ctx.Value()\n    config        config          // internal runtime config\n    // ...\n}",{"id":197,"title":198,"titles":199,"content":200,"level":40},"/v1.0.3/learn/architecture#schema","Schema",[16,189],"User-facing configuration format (YAML/JSON): type Schema struct {\n    Metrics []MetricSchema\n    Traces  []TraceSchema\n    Logs    *LogSchema\n    Context *ContextSchema\n    Stdout  bool\n}",{"id":202,"title":203,"titles":204,"content":205,"level":40},"/v1.0.3/learn/architecture#metricschema","MetricSchema",[16,189],"Defines how a signal becomes a metric: type MetricSchema struct {\n    Signal      string  // Signal name to match\n    Name        string  // OTEL metric name\n    Type        string  // counter, gauge, histogram, updowncounter\n    ValueKey    string  // Field name for value (non-counters)\n    Description string\n}",{"id":207,"title":208,"titles":209,"content":210,"level":40},"/v1.0.3/learn/architecture#traceschema","TraceSchema",[16,189],"Defines span correlation: type TraceSchema struct {\n    Start          string  // Signal name that starts span\n    End            string  // Signal name that ends span\n    CorrelationKey string  // String field name to match start/end\n    SpanName       string\n    SpanTimeout    string  // e.g., \"5m\", \"30s\"\n}",{"id":212,"title":148,"titles":213,"content":214,"level":19},"/v1.0.3/learn/architecture#name-based-matching",[16],"Schema uses string names that match at runtime via event.Signal().Name(): // Schema specifies signal by name\nschema := Schema{\n    Metrics: []MetricSchema{\n        {Signal: \"order.created\", Name: \"orders_total\", Type: \"counter\"},\n    },\n}\n\n// At runtime, handler compares:\nif event.Signal().Name() == metricConfig.SignalName {\n    // Increment counter\n} This enables hot-reload without recompilation.",{"id":216,"title":119,"titles":217,"content":218,"level":19},"/v1.0.3/learn/architecture#field-transformation",[16],"The transform component handles field-to-attribute conversion.",{"id":220,"title":221,"titles":222,"content":223,"level":40},"/v1.0.3/learn/architecture#built-in-types","Built-in Types",[16,119],"Direct mapping for primitive types: Capitan TypeOTEL Attributestringlog.String(key, value)int, int32, int64log.Int64(key, value)float32, float64log.Float64(key, value)boollog.Bool(key, value)time.Timelog.String(key, rfc3339)time.Durationlog.Int64(key, millis)[]bytelog.Bytes(key, value)Custom typeslog.String(key, json)",{"id":225,"title":226,"titles":227,"content":228,"level":40},"/v1.0.3/learn/architecture#custom-type-handling","Custom Type Handling",[16,119],"Custom types are automatically JSON serialized: type OrderInfo struct {\n    ID    string  `json:\"id\"`\n    Total float64 `json:\"total\"`\n}\n\n// Becomes: log.String(\"order\", \"{\\\"id\\\":\\\"ORD-123\\\",\\\"total\\\":99.99}\") Use JSON struct tags to control serialization.",{"id":230,"title":231,"titles":232,"content":233,"level":19},"/v1.0.3/learn/architecture#trace-correlation","Trace Correlation",[16],"The trace handler maintains pending spans in a map keyed by composite key (signal name + correlation value): type pendingSpan struct {\n    ctx        context.Context\n    span       trace.Span\n    startTime  time.Time\n    receivedAt time.Time\n}\n\n// Key format: \"signalName:correlationValue\"\npendingStarts map[string]*pendingSpan\npendingEnds   map[string]*pendingEnd",{"id":235,"title":236,"titles":237,"content":238,"level":40},"/v1.0.3/learn/architecture#flow","Flow",[16,231],"Start signal arrives with correlation key valueNew span created, stored in pending mapCleanup goroutine monitors for timeoutsEnd signal arrives with matching correlation valueSpan retrieved from pending map and ended",{"id":240,"title":241,"titles":242,"content":243,"level":40},"/v1.0.3/learn/architecture#out-of-order-events","Out-of-Order Events",[16,231],"Aperture handles out-of-order delivery gracefully: If end arrives before start, end is stored in pendingEndsWhen start arrives, it checks pendingEnds firstSpan created with correct timestamps from both events This works because capitan events capture timestamp at emission time, not delivery time.",{"id":245,"title":153,"titles":246,"content":247,"level":19},"/v1.0.3/learn/architecture#context-extraction",[16],"Context keys must be registered, then referenced by name in schema: // Registration (requires actual Go type)\nap.RegisterContextKey(\"user_id\", userIDKey)\n\n// Schema references by name\nschema := Schema{\n    Context: &ContextSchema{\n        Logs: []string{\"user_id\"},\n    },\n} On each event: for _, ctxKey := range config.ContextExtraction.Logs {\n    if value := ctx.Value(ctxKey.Key); value != nil {\n        attrs = append(attrs, transformContextValue(ctxKey.Name, value))\n    }\n}",{"id":249,"title":250,"titles":251,"content":252,"level":19},"/v1.0.3/learn/architecture#thread-safety","Thread Safety",[16],"Aperture is safe for concurrent useObserver callback runs on capitan's worker goroutine per signalPending span map uses mutex protectionProvider calls are delegated to OTEL (thread-safe by design)",{"id":254,"title":255,"titles":256,"content":257,"level":19},"/v1.0.3/learn/architecture#memory-management","Memory Management",[16],"No event buffering beyond capitan's internal buffersPending spans cleaned up on timeoutMetrics use OTEL's instrument pooling",{"id":259,"title":260,"titles":261,"content":262,"level":19},"/v1.0.3/learn/architecture#error-handling","Error Handling",[16],"Provider errors logged but not propagated (best-effort observability)Invalid schemas rejected at Apply() timeMissing correlation keys logged as warningsTransformation errors result in attribute omission, not failure",{"id":264,"title":265,"titles":266,"content":267,"level":40},"/v1.0.3/learn/architecture#diagnostic-signals","Diagnostic Signals",[16,260],"Aperture emits internal signals at DEBUG severity for operational visibility: SignalWhen EmittedResolutionaperture:metric:value_missingGauge/histogram event lacks value fieldEnsure event includes the required value fieldaperture:trace:correlation_missingTrace event lacks correlation fieldEnsure event includes the correlation fieldaperture:trace:expiredSpan start/end never matched within timeoutCheck correlation IDs match, or increase timeout",{"id":269,"title":270,"titles":271,"content":272,"level":19},"/v1.0.3/learn/architecture#hot-reload","Hot Reload",[16],"The Apply() method enables runtime configuration updates: func (s *Aperture) Apply(schema Schema) error {\n    // 1. Validate schema\n    // 2. Build internal config\n    // 3. Close old observer\n    // 4. Create new observer with new config\n} Events during the swap may be dropped. For most applications this is acceptable. html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}",{"id":274,"title":138,"titles":275,"content":276,"level":9},"/v1.0.3/guides/metrics",[],"Configure signal-to-metric transformations",{"id":278,"title":279,"titles":280,"content":281,"level":9},"/v1.0.3/guides/metrics#metrics-guide","Metrics Guide",[],"Transform capitan signals into OTEL metrics.",{"id":283,"title":284,"titles":285,"content":34,"level":19},"/v1.0.3/guides/metrics#metric-types","Metric Types",[279],{"id":287,"title":288,"titles":289,"content":290,"level":40},"/v1.0.3/guides/metrics#counter","Counter",[279,284],"Increments on each signal emission. No value extraction needed. orderCreated := capitan.NewSignal(\"order.created\", \"Order created\")\n\nschema := aperture.Schema{\n    Metrics: []aperture.MetricSchema{\n        {\n            Signal:      \"order.created\",\n            Name:        \"orders_created_total\",\n            Type:        \"counter\",\n            Description: \"Total number of orders created\",\n        },\n    },\n}\n\nap, _ := aperture.New(cap, logProvider, meterProvider, traceProvider)\nap.Apply(schema)\n\n// Each emission increments by 1\ncap.Emit(ctx, orderCreated)  // orders_created_total += 1\ncap.Emit(ctx, orderCreated)  // orders_created_total += 1",{"id":292,"title":293,"titles":294,"content":295,"level":40},"/v1.0.3/guides/metrics#gauge","Gauge",[279,284],"Records the current value from a field. Useful for instantaneous measurements. cpuUsage := capitan.NewSignal(\"system.cpu\", \"CPU measurement\")\npercentKey := capitan.NewFloat64Key(\"percent\")\n\nschema := aperture.Schema{\n    Metrics: []aperture.MetricSchema{\n        {\n            Signal:   \"system.cpu\",\n            Name:     \"cpu_usage_percent\",\n            Type:     \"gauge\",\n            ValueKey: \"percent\",\n        },\n    },\n}\n\n// Records the value from the percent field\ncap.Emit(ctx, cpuUsage, percentKey.Field(45.2))  // cpu_usage_percent = 45.2\ncap.Emit(ctx, cpuUsage, percentKey.Field(67.8))  // cpu_usage_percent = 67.8",{"id":297,"title":298,"titles":299,"content":300,"level":40},"/v1.0.3/guides/metrics#histogram","Histogram",[279,284],"Records value distributions. Ideal for latencies, sizes, or any value you want percentiles for. requestDone := capitan.NewSignal(\"request.done\", \"Request completed\")\ndurationKey := capitan.NewDurationKey(\"duration\")\n\nschema := aperture.Schema{\n    Metrics: []aperture.MetricSchema{\n        {\n            Signal:   \"request.done\",\n            Name:     \"request_duration_ms\",\n            Type:     \"histogram\",\n            ValueKey: \"duration\",\n        },\n    },\n}\n\n// Records duration values in the distribution\ncap.Emit(ctx, requestDone, durationKey.Field(10*time.Millisecond))\ncap.Emit(ctx, requestDone, durationKey.Field(250*time.Millisecond))\ncap.Emit(ctx, requestDone, durationKey.Field(50*time.Millisecond))",{"id":302,"title":303,"titles":304,"content":305,"level":40},"/v1.0.3/guides/metrics#updowncounter","UpDownCounter",[279,284],"Bidirectional counter for values that increase and decrease. queueChanged := capitan.NewSignal(\"queue.changed\", \"Queue size changed\")\ndeltaKey := capitan.NewInt64Key(\"delta\")\n\nschema := aperture.Schema{\n    Metrics: []aperture.MetricSchema{\n        {\n            Signal:   \"queue.changed\",\n            Name:     \"queue_depth\",\n            Type:     \"updowncounter\",\n            ValueKey: \"delta\",\n        },\n    },\n}\n\n// Track queue depth changes\ncap.Emit(ctx, queueChanged, deltaKey.Field(int64(5)))   // queue_depth += 5\ncap.Emit(ctx, queueChanged, deltaKey.Field(int64(-2)))  // queue_depth -= 2",{"id":307,"title":308,"titles":309,"content":310,"level":19},"/v1.0.3/guides/metrics#dimensions-attributes","Dimensions (Attributes)",[279],"Event fields automatically become metric dimensions: orderCreated := capitan.NewSignal(\"order.created\", \"Order created\")\nregionKey := capitan.NewStringKey(\"region\")\ntierKey := capitan.NewStringKey(\"tier\")\n\nschema := aperture.Schema{\n    Metrics: []aperture.MetricSchema{\n        {Signal: \"order.created\", Name: \"orders_total\", Type: \"counter\"},\n    },\n}\n\n// Metrics include region and tier as dimensions\ncap.Emit(ctx, orderCreated, regionKey.Field(\"us-east\"), tierKey.Field(\"premium\"))\ncap.Emit(ctx, orderCreated, regionKey.Field(\"eu-west\"), tierKey.Field(\"standard\")) Produces: orders_total{region=\"us-east\", tier=\"premium\"} = 1\norders_total{region=\"eu-west\", tier=\"standard\"} = 1",{"id":312,"title":313,"titles":314,"content":315,"level":19},"/v1.0.3/guides/metrics#cardinality-warning","Cardinality Warning",[279],"High-cardinality dimensions exponentially increase storage: // GOOD: Low cardinality\nregionKey := capitan.NewStringKey(\"region\")      // ~10 values\ntierKey := capitan.NewStringKey(\"tier\")          // ~3 values\n\n// BAD: High cardinality\nuserIDKey := capitan.NewStringKey(\"user_id\")     // ~millions of values\nrequestIDKey := capitan.NewStringKey(\"request_id\") // ~infinite values Use context extraction carefully for metrics: ap.RegisterContextKey(\"region\", regionKey)\n\nschema := aperture.Schema{\n    Context: &aperture.ContextSchema{\n        Metrics: []string{\"region\"},    // OK\n        // Metrics: []string{\"user_id\"}, // Avoid high-cardinality\n    },\n}",{"id":317,"title":318,"titles":319,"content":320,"level":19},"/v1.0.3/guides/metrics#value-key-types","Value Key Types",[279],"For gauges, histograms, and up-down counters, the ValueKey extracts the numeric value: Key TypeMetric ValueInt64KeyInt64 valueFloat64KeyFloat64 valueDurationKeyFloat64 millisecondsIntKeyInt64 (converted)UintKeyInt64 (converted) // Duration key extracts milliseconds\ndurationKey := capitan.NewDurationKey(\"duration\")\n\n// 100ms becomes 100.0\ncap.Emit(ctx, sig, durationKey.Field(100*time.Millisecond))",{"id":322,"title":323,"titles":324,"content":325,"level":19},"/v1.0.3/guides/metrics#multiple-metrics-per-signal","Multiple Metrics per Signal",[279],"One signal can trigger multiple metrics: requestDone := capitan.NewSignal(\"request.done\", \"Request completed\")\ndurationKey := capitan.NewDurationKey(\"duration\")\n\nschema := aperture.Schema{\n    Metrics: []aperture.MetricSchema{\n        // Count total requests\n        {\n            Signal: \"request.done\",\n            Name:   \"requests_total\",\n            Type:   \"counter\",\n        },\n        // Record latency distribution\n        {\n            Signal:   \"request.done\",\n            Name:     \"request_duration_ms\",\n            Type:     \"histogram\",\n            ValueKey: \"duration\",\n        },\n    },\n}\n\n// Both metrics updated\ncap.Emit(ctx, requestDone, durationKey.Field(50*time.Millisecond))",{"id":327,"title":328,"titles":329,"content":330,"level":19},"/v1.0.3/guides/metrics#missing-values","Missing Values",[279],"If a gauge/histogram/updowncounter emission lacks the value key: Metric operation is skipped for that emissionNo error (best-effort approach) schema := aperture.Schema{\n    Metrics: []aperture.MetricSchema{\n        {Signal: \"test.signal\", Name: \"gauge\", Type: \"gauge\", ValueKey: \"value\"},\n    },\n}\n\ncap.Emit(ctx, sig)  // No value field - gauge update skipped\ncap.Emit(ctx, sig, valueKey.Field(42.0))  // gauge = 42.0",{"id":332,"title":333,"titles":334,"content":335,"level":19},"/v1.0.3/guides/metrics#schema-configuration","Schema Configuration",[279],"Via YAML: metrics:\n  - signal: order.created\n    name: orders_total\n    type: counter\n    description: Total orders placed\n\n  - signal: request.done\n    name: request_duration_ms\n    type: histogram\n    value_key: duration\n\n  - signal: queue.changed\n    name: queue_depth\n    type: updowncounter\n    value_key: delta Load and apply: configBytes, _ := os.ReadFile(\"config.yaml\")\nschema, _ := aperture.LoadSchemaFromYAML(configBytes)\nap.Apply(schema) html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}",{"id":337,"title":143,"titles":338,"content":339,"level":9},"/v1.0.3/guides/traces",[],"Configure signal pair correlation into spans",{"id":341,"title":342,"titles":343,"content":344,"level":9},"/v1.0.3/guides/traces#traces-guide","Traces Guide",[],"Correlate capitan signal pairs into OTEL spans.",{"id":346,"title":347,"titles":348,"content":349,"level":19},"/v1.0.3/guides/traces#basic-correlation","Basic Correlation",[342],"A span is created by matching start and end signals via a correlation key: requestStarted := capitan.NewSignal(\"request.started\", \"Request started\")\nrequestCompleted := capitan.NewSignal(\"request.completed\", \"Request completed\")\nrequestID := capitan.NewStringKey(\"request_id\")\n\nschema := aperture.Schema{\n    Traces: []aperture.TraceSchema{\n        {\n            Start:          \"request.started\",\n            End:            \"request.completed\",\n            CorrelationKey: \"request_id\",\n            SpanName:       \"http_request\",\n        },\n    },\n}\n\nap, _ := aperture.New(cap, logProvider, meterProvider, traceProvider)\nap.Apply(schema)\n\n// Start span\ncap.Emit(ctx, requestStarted, requestID.Field(\"REQ-123\"))\n// ... request processing ...\n\n// End span\ncap.Emit(ctx, requestCompleted, requestID.Field(\"REQ-123\")) The correlation key value REQ-123 links the start and end events. Note: The correlation key must reference a StringKey. String values provide reliable correlation matching.",{"id":351,"title":352,"titles":353,"content":354,"level":19},"/v1.0.3/guides/traces#span-attributes","Span Attributes",[342],"Fields from both start and end events become span attributes: methodKey := capitan.NewStringKey(\"method\")\nstatusKey := capitan.NewIntKey(\"status\")\ndurationKey := capitan.NewDurationKey(\"duration\")\n\n// Start event fields\ncap.Emit(ctx, requestStarted,\n    requestID.Field(\"REQ-123\"),\n    methodKey.Field(\"GET\"),\n)\n\n// End event fields\ncap.Emit(ctx, requestCompleted,\n    requestID.Field(\"REQ-123\"),\n    statusKey.Field(200),\n    durationKey.Field(150*time.Millisecond),\n)\n\n// Span includes: method=\"GET\", status=200, duration=150000000",{"id":356,"title":357,"titles":358,"content":359,"level":19},"/v1.0.3/guides/traces#span-timeout","Span Timeout",[342],"Configure maximum time to wait for an end event: schema := aperture.Schema{\n    Traces: []aperture.TraceSchema{\n        {\n            Start:          \"request.started\",\n            End:            \"request.completed\",\n            CorrelationKey: \"request_id\",\n            SpanName:       \"http_request\",\n            SpanTimeout:    \"5m\",  // Default: 5m\n        },\n    },\n} If end doesn't arrive within timeout: Span is ended with timeout statusMemory for pending span is released Timeout values use Go duration syntax: 5m, 30s, 1h, 500ms.",{"id":361,"title":362,"titles":363,"content":364,"level":19},"/v1.0.3/guides/traces#concurrent-spans","Concurrent Spans",[342],"Multiple spans can be in-flight simultaneously: // Multiple requests overlapping\ncap.Emit(ctx, reqStarted, requestID.Field(\"REQ-001\"))\ncap.Emit(ctx, reqStarted, requestID.Field(\"REQ-002\"))\ncap.Emit(ctx, reqStarted, requestID.Field(\"REQ-003\"))\n\n// End in any order\ncap.Emit(ctx, reqCompleted, requestID.Field(\"REQ-002\"))  // REQ-002 span ends\ncap.Emit(ctx, reqCompleted, requestID.Field(\"REQ-001\"))  // REQ-001 span ends\ncap.Emit(ctx, reqCompleted, requestID.Field(\"REQ-003\"))  // REQ-003 span ends Each span tracks independently via correlation value.",{"id":366,"title":241,"titles":367,"content":368,"level":19},"/v1.0.3/guides/traces#out-of-order-events",[342],"Aperture handles out-of-order event delivery gracefully.",{"id":370,"title":371,"titles":372,"content":373,"level":40},"/v1.0.3/guides/traces#why-this-happens","Why This Happens",[342,241],"Capitan processes observers per-signal, not globally. Each signal has its own execution queue, so observers for request.completed may execute before observers for request.started—even if the start was emitted first. This is a feature, not a bug: it prevents slow observers on one signal from blocking others.",{"id":375,"title":376,"titles":377,"content":378,"level":40},"/v1.0.3/guides/traces#why-it-doesnt-matter","Why It Doesn't Matter",[342,241],"Capitan events capture their timestamp at emission time, not delivery time. Aperture uses these emission timestamps when creating spans, so delivery order is irrelevant to span accuracy.",{"id":380,"title":381,"titles":382,"content":383,"level":40},"/v1.0.3/guides/traces#how-it-works","How It Works",[342,241],"If the end event arrives before the start: End event data is stored (correlation ID, timestamp, context)When start arrives, both timestamps are used to create the spanSpan duration is calculated correctly from start to end // End delivered before start (different signal queues)\ncap.Emit(ctx, reqCompleted, requestID.Field(\"REQ-123\"))  // Stored, waiting for start\ncap.Emit(ctx, reqStarted, requestID.Field(\"REQ-123\"))    // Span created with correct timestamps\n\n// Result: span with accurate start time, end time, and duration This design ensures trace accuracy regardless of observer execution order.",{"id":385,"title":386,"titles":387,"content":388,"level":19},"/v1.0.3/guides/traces#missing-correlation-key","Missing Correlation Key",[342],"If an event lacks the correlation key: Event is logged (if log config allows)Trace correlation is skippedWarning logged about missing key // Missing requestID field - no span correlation\ncap.Emit(ctx, reqStarted)  // Logged, but no span started",{"id":390,"title":391,"titles":392,"content":393,"level":19},"/v1.0.3/guides/traces#multiple-trace-configurations","Multiple Trace Configurations",[342],"Different signal pairs can create different spans: schema := aperture.Schema{\n    Traces: []aperture.TraceSchema{\n        {\n            Start:          \"http.request.started\",\n            End:            \"http.request.done\",\n            CorrelationKey: \"request_id\",\n            SpanName:       \"http_request\",\n        },\n        {\n            Start:          \"db.query.started\",\n            End:            \"db.query.done\",\n            CorrelationKey: \"query_id\",\n            SpanName:       \"db_query\",\n        },\n        {\n            Start:          \"cache.op.started\",\n            End:            \"cache.op.done\",\n            CorrelationKey: \"cache_key\",\n            SpanName:       \"cache_op\",\n        },\n    },\n}",{"id":395,"title":396,"titles":397,"content":398,"level":19},"/v1.0.3/guides/traces#span-context-propagation","Span Context Propagation",[342],"Spans inherit trace context from the event context: // Parent span (created via direct OTEL)\nctx, parentSpan := ap.Tracer(\"orders\").Start(ctx, \"process-order\")\ndefer parentSpan.End()\n\n// Child span (created via signal correlation)\ncap.Emit(ctx, paymentStarted, paymentID.Field(\"PAY-123\"))\n// ... payment processing ...\ncap.Emit(ctx, paymentCompleted, paymentID.Field(\"PAY-123\"))\n// ^ This span is a child of process-order",{"id":400,"title":401,"titles":402,"content":403,"level":19},"/v1.0.3/guides/traces#context-extraction-for-traces","Context Extraction for Traces",[342],"Add context values as span attributes: type ctxKey string\nconst userIDKey ctxKey = \"user_id\"\n\nap.RegisterContextKey(\"user_id\", userIDKey)\n\nschema := aperture.Schema{\n    Traces: []aperture.TraceSchema{\n        {\n            Start:          \"request.started\",\n            End:            \"request.completed\",\n            CorrelationKey: \"request_id\",\n            SpanName:       \"request\",\n        },\n    },\n    Context: &aperture.ContextSchema{\n        Traces: []string{\"user_id\"},\n    },\n}\nap.Apply(schema)\n\nctx = context.WithValue(ctx, userIDKey, \"user-456\")\ncap.Emit(ctx, reqStarted, requestID.Field(\"REQ-789\"))\n// ^ Span includes user_id=\"user-456\" attribute",{"id":405,"title":406,"titles":407,"content":408,"level":19},"/v1.0.3/guides/traces#using-tracer-directly","Using Tracer Directly",[342],"Access the underlying OTEL tracer for manual spans: tracer := ap.Tracer(\"orders\")\n\n// Manual span creation\nctx, span := tracer.Start(ctx, \"custom-operation\")\ndefer span.End()\n\n// Span attributes\nspan.SetAttributes(attribute.String(\"order_id\", \"ORD-123\"))\n\n// Events within span\nspan.AddEvent(\"payment-processed\") Signal correlation and direct tracer use work together.",{"id":410,"title":333,"titles":411,"content":412,"level":19},"/v1.0.3/guides/traces#schema-configuration",[342],"Via YAML: traces:\n  - start: request.started\n    end: request.completed\n    correlation_key: request_id\n    span_name: http-request\n    span_timeout: 5m\n\n  - start: db.query.started\n    end: db.query.completed\n    correlation_key: query_id\n    span_name: db-query\n    span_timeout: 30s Load and apply: configBytes, _ := os.ReadFile(\"config.yaml\")\nschema, _ := aperture.LoadSchemaFromYAML(configBytes)\nap.Apply(schema) html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sfm-E, html code.shiki .sfm-E{--shiki-default:var(--shiki-variable)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}",{"id":414,"title":133,"titles":415,"content":416,"level":9},"/v1.0.3/guides/logs",[],"Configure event-to-log transformations",{"id":418,"title":419,"titles":420,"content":421,"level":9},"/v1.0.3/guides/logs#logs-guide","Logs Guide",[],"Transform capitan events into OTEL logs.",{"id":423,"title":424,"titles":425,"content":426,"level":19},"/v1.0.3/guides/logs#default-behavior","Default Behavior",[419],"With no log configuration, all events are logged: // No log config = log everything\nap, _ := aperture.New(cap, logProvider, meterProvider, traceProvider)\n\nschema := aperture.Schema{\n    Metrics: []aperture.MetricSchema{...},\n    // Logs: nil - all events logged\n}\nap.Apply(schema)\n\nsig1 := capitan.NewSignal(\"order.created\", \"Order created\")\nsig2 := capitan.NewSignal(\"order.shipped\", \"Order shipped\")\n\ncap.Emit(ctx, sig1)  // Logged\ncap.Emit(ctx, sig2)  // Logged",{"id":428,"title":429,"titles":430,"content":431,"level":19},"/v1.0.3/guides/logs#whitelist-filtering","Whitelist Filtering",[419],"Log only specific signals: orderCreated := capitan.NewSignal(\"order.created\", \"Order created\")\norderFailed := capitan.NewSignal(\"order.failed\", \"Order failed\")\norderShipped := capitan.NewSignal(\"order.shipped\", \"Order shipped\")\n\nschema := aperture.Schema{\n    Logs: &aperture.LogSchema{\n        Whitelist: []string{\n            \"order.created\",\n            \"order.failed\",\n        },\n    },\n}\n\ncap.Emit(ctx, orderCreated)  // Logged\ncap.Emit(ctx, orderFailed)   // Logged\ncap.Emit(ctx, orderShipped)  // NOT logged (not in whitelist)",{"id":433,"title":434,"titles":435,"content":436,"level":19},"/v1.0.3/guides/logs#log-attributes","Log Attributes",[419],"Event fields become log attributes: orderCreated := capitan.NewSignal(\"order.created\", \"Order created\")\norderID := capitan.NewStringKey(\"order_id\")\ntotal := capitan.NewFloat64Key(\"total\")\nitems := capitan.NewIntKey(\"items\")\n\ncap.Emit(ctx, orderCreated,\n    orderID.Field(\"ORD-123\"),\n    total.Field(99.99),\n    items.Field(3),\n) Produces log record with attributes: signal=\"order.created\"\norder_id=\"ORD-123\"\ntotal=99.99\nitems=3",{"id":438,"title":439,"titles":440,"content":441,"level":19},"/v1.0.3/guides/logs#signal-metadata","Signal Metadata",[419],"Every log record includes standard attributes: AttributeSourceDescriptioncapitan.signalEvent.Signal()Signal namecapitan.signal.descriptionSignal descriptionSignal descriptionTimestampEvent.Timestamp()Event timestampSeverityEvent.Severity()Capitan severity level",{"id":443,"title":444,"titles":445,"content":446,"level":19},"/v1.0.3/guides/logs#severity-mapping","Severity Mapping",[419],"Capitan severity maps to OTEL log severity: CapitanOTELDebugDEBUGInfoINFOWarnWARNErrorERRORFatalFATAL",{"id":448,"title":449,"titles":450,"content":451,"level":19},"/v1.0.3/guides/logs#context-extraction-for-logs","Context Extraction for Logs",[419],"Add context values as log attributes: type ctxKey string\nconst (\n    userIDKey  ctxKey = \"user_id\"\n    requestKey ctxKey = \"request_id\"\n)\n\nap.RegisterContextKey(\"user_id\", userIDKey)\nap.RegisterContextKey(\"request_id\", requestKey)\n\nschema := aperture.Schema{\n    Context: &aperture.ContextSchema{\n        Logs: []string{\"user_id\", \"request_id\"},\n    },\n}\nap.Apply(schema)\n\nctx = context.WithValue(ctx, userIDKey, \"user-123\")\nctx = context.WithValue(ctx, requestKey, \"req-456\")\n\ncap.Emit(ctx, sig)\n// Log includes: user_id=\"user-123\", request_id=\"req-456\"",{"id":453,"title":454,"titles":455,"content":456,"level":19},"/v1.0.3/guides/logs#stdout-logging","Stdout Logging",[419],"Enable console logging alongside OTEL: schema := aperture.Schema{\n    Stdout: true,\n}\n\ncap.Emit(ctx, sig)\n// Logs to both OTEL and stdout Output format: time=2025-01-15T10:30:00-08:00 level=INFO msg=\"Order created\" signal=order.created order_id=ORD-123",{"id":458,"title":226,"titles":459,"content":460,"level":19},"/v1.0.3/guides/logs#custom-type-handling",[419],"Custom types are automatically JSON serialized: type OrderInfo struct {\n    ID     string  `json:\"id\"`\n    Total  float64 `json:\"total\"`\n    Secret string  `json:\"-\"`  // Excluded from JSON\n}\n\norderKey := capitan.NewKey[OrderInfo](\"order\", \"Order details\")\n\ncap.Emit(ctx, sig, orderKey.Field(OrderInfo{\n    ID:     \"ORD-123\",\n    Total:  99.99,\n    Secret: \"xxx\",\n}))\n// Log includes: order=\"{\\\"id\\\":\\\"ORD-123\\\",\\\"total\\\":99.99}\"\n// Secret excluded via json:\"-\" tag Use JSON struct tags to control what gets exported.",{"id":462,"title":463,"titles":464,"content":465,"level":19},"/v1.0.3/guides/logs#using-logger-directly","Using Logger Directly",[419],"Access the underlying OTEL logger: logger := ap.Logger(\"orders\")\n\n// Direct log emission\nvar record log.Record\nrecord.SetBody(log.StringValue(\"Direct log message\"))\nrecord.SetSeverity(log.SeverityInfo)\nrecord.AddAttributes(log.String(\"order_id\", \"ORD-123\"))\n\nlogger.Emit(ctx, record)",{"id":467,"title":468,"titles":469,"content":470,"level":19},"/v1.0.3/guides/logs#built-in-field-types","Built-in Field Types",[419],"All capitan field types are transformed: Capitan TypeLog Attributestringlog.String(key, value)int, int32, int64log.Int64(key, value)uint, uint32, uint64log.Int64(key, value)float32, float64log.Float64(key, value)boollog.Bool(key, value)time.Timelog.String(key, rfc3339)time.Durationlog.Int64(key, millis)[]bytelog.Bytes(key, value)Custom typeslog.String(key, json)",{"id":472,"title":333,"titles":473,"content":474,"level":19},"/v1.0.3/guides/logs#schema-configuration",[419],"Via YAML: logs:\n  whitelist:\n    - order.created\n    - order.failed\n    - payment.failed\n\nstdout: true Load and apply: configBytes, _ := os.ReadFile(\"config.yaml\")\nschema, _ := aperture.LoadSchemaFromYAML(configBytes)\nap.Apply(schema)",{"id":476,"title":477,"titles":478,"content":479,"level":19},"/v1.0.3/guides/logs#performance-considerations","Performance Considerations",[419],"Whitelist filtering happens before transformation (fast path for filtered events)Field transformation is lazy (only when logging)Stdout logging adds overhead; disable in production if not needed html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sfm-E, html code.shiki .sfm-E{--shiki-default:var(--shiki-variable)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}",{"id":481,"title":153,"titles":482,"content":483,"level":9},"/v1.0.3/guides/context",[],"Enrich signals with context values",{"id":485,"title":486,"titles":487,"content":488,"level":9},"/v1.0.3/guides/context#context-extraction-guide","Context Extraction Guide",[],"Extract values from context.Context and add them as attributes to logs, metrics, and traces.",{"id":490,"title":491,"titles":492,"content":493,"level":19},"/v1.0.3/guides/context#registration","Registration",[486],"Context keys must be registered before use: type ctxKey string\nconst (\n    userIDKey    ctxKey = \"user_id\"\n    regionKey    ctxKey = \"region\"\n    requestIDKey ctxKey = \"request_id\"\n)\n\nap, _ := aperture.New(cap, logProvider, meterProvider, traceProvider)\n\n// Register context keys by name\nap.RegisterContextKey(\"user_id\", userIDKey)\nap.RegisterContextKey(\"region\", regionKey)\nap.RegisterContextKey(\"request_id\", requestIDKey)",{"id":495,"title":496,"titles":497,"content":498,"level":19},"/v1.0.3/guides/context#configuration","Configuration",[486],"Specify which context keys to extract for each signal type: schema := aperture.Schema{\n    Context: &aperture.ContextSchema{\n        Logs: []string{\"user_id\", \"request_id\"},\n        Metrics: []string{\"region\"},  // Low cardinality only\n        Traces: []string{\"user_id\", \"region\"},\n    },\n}\nap.Apply(schema)",{"id":500,"title":501,"titles":502,"content":503,"level":19},"/v1.0.3/guides/context#usage","Usage",[486],"Add values to context, then emit events: ctx := context.Background()\nctx = context.WithValue(ctx, userIDKey, \"user-123\")\nctx = context.WithValue(ctx, regionKey, \"us-east-1\")\nctx = context.WithValue(ctx, requestIDKey, \"req-456\")\n\ncap.Emit(ctx, orderCreated, orderID.Field(\"ORD-789\")) Results: Log: user_id=\"user-123\", request_id=\"req-456\", order_id=\"ORD-789\"Metric: region=\"us-east-1\" dimensionTrace span: user_id=\"user-123\", region=\"us-east-1\" attributes",{"id":505,"title":506,"titles":507,"content":508,"level":19},"/v1.0.3/guides/context#supported-value-types","Supported Value Types",[486],"Context values are converted to attributes: Go TypeOTEL AttributestringStringint, int32, int64Int64uint, uint32, uint64Int64float32, float64Float64boolBool[]byteBytes Other types are skipped silently.",{"id":510,"title":328,"titles":511,"content":512,"level":19},"/v1.0.3/guides/context#missing-values",[486],"If a context key is configured but not present: The attribute is simply not addedNo error or warningOther attributes still extracted schema := aperture.Schema{\n    Context: &aperture.ContextSchema{\n        Logs: []string{\"user_id\", \"session_id\"},\n    },\n}\n\n// Only user_id set\nctx = context.WithValue(ctx, userIDKey, \"user-123\")\n\ncap.Emit(ctx, sig)\n// Log includes user_id=\"user-123\"\n// session_id not present, not added",{"id":514,"title":515,"titles":516,"content":517,"level":19},"/v1.0.3/guides/context#cardinality-warning-for-metrics","Cardinality Warning for Metrics",[486],"Metric dimensions multiply storage: // GOOD for metrics: bounded, low cardinality\nregionKey    // ~10-20 values\ntierKey      // ~3 values\nenvironmentKey // ~3 values\n\n// BAD for metrics: unbounded, high cardinality\nuserIDKey    // ~millions of values\nrequestIDKey // ~infinite values\nsessionKey   // ~millions of values Separate extraction configs let you be selective: schema := aperture.Schema{\n    Context: &aperture.ContextSchema{\n        // High cardinality OK for logs and traces\n        Logs: []string{\"user_id\", \"request_id\"},\n        Traces: []string{\"user_id\", \"request_id\"},\n        // Only low cardinality for metrics\n        Metrics: []string{\"region\", \"tier\"},\n    },\n}",{"id":519,"title":520,"titles":521,"content":522,"level":19},"/v1.0.3/guides/context#middleware-pattern","Middleware Pattern",[486],"Common pattern: middleware adds context values that propagate through request handling: func RequestContextMiddleware(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        ctx := r.Context()\n\n        // Add request ID\n        requestID := r.Header.Get(\"X-Request-ID\")\n        if requestID == \"\" {\n            requestID = uuid.New().String()\n        }\n        ctx = context.WithValue(ctx, requestIDKey, requestID)\n\n        // Add user from auth\n        if userID := getUserFromAuth(r); userID != \"\" {\n            ctx = context.WithValue(ctx, userIDKey, userID)\n        }\n\n        // Add region from header or default\n        region := r.Header.Get(\"X-Region\")\n        if region == \"\" {\n            region = \"us-east-1\"\n        }\n        ctx = context.WithValue(ctx, regionKey, region)\n\n        next.ServeHTTP(w, r.WithContext(ctx))\n    })\n} All events emitted during request handling automatically include these values.",{"id":524,"title":525,"titles":526,"content":527,"level":19},"/v1.0.3/guides/context#combining-with-event-fields","Combining with Event Fields",[486],"Context extraction complements event fields: // Context: request-scoped values\nctx = context.WithValue(ctx, userIDKey, \"user-123\")\nctx = context.WithValue(ctx, regionKey, \"us-east-1\")\n\n// Fields: event-specific values\ncap.Emit(ctx, orderCreated,\n    orderID.Field(\"ORD-456\"),\n    total.Field(99.99),\n)\n\n// Log includes all:\n// user_id=\"user-123\" (from context)\n// region=\"us-east-1\" (from context)\n// order_id=\"ORD-456\" (from field)\n// total=99.99 (from field)",{"id":529,"title":333,"titles":530,"content":531,"level":19},"/v1.0.3/guides/context#schema-configuration",[486],"Via YAML: context:\n  logs:\n    - user_id\n    - request_id\n  metrics:\n    - region\n    - tier\n  traces:\n    - user_id\n    - region Register context keys before applying: ap.RegisterContextKey(\"user_id\", userIDKey)\nap.RegisterContextKey(\"request_id\", requestIDKey)\nap.RegisterContextKey(\"region\", regionKey)\nap.RegisterContextKey(\"tier\", tierKey)\n\nconfigBytes, _ := os.ReadFile(\"config.yaml\")\nschema, _ := aperture.LoadSchemaFromYAML(configBytes)\nap.Apply(schema) html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sfm-E, html code.shiki .sfm-E{--shiki-default:var(--shiki-variable)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}",{"id":533,"title":333,"titles":534,"content":535,"level":9},"/v1.0.3/guides/schema",[],"File-based configuration with hot-reload support",{"id":537,"title":538,"titles":539,"content":540,"level":9},"/v1.0.3/guides/schema#schema-configuration-guide","Schema Configuration Guide",[],"Load aperture configuration from YAML or JSON files for runtime flexibility.",{"id":542,"title":543,"titles":544,"content":545,"level":19},"/v1.0.3/guides/schema#why-schema-based-config","Why Schema-Based Config",[538],"Separation: Configuration in files, types in codeHot-reload: Update behavior without redeploymentValidation: Catch errors at load timeTooling: Generate documentation from schemas",{"id":547,"title":548,"titles":549,"content":550,"level":19},"/v1.0.3/guides/schema#loading-schemas","Loading Schemas",[538],"// From file\nconfigBytes, err := os.ReadFile(\"observability.yaml\")\nif err != nil {\n    log.Fatal(err)\n}\nschema, err := aperture.LoadSchemaFromYAML(configBytes)\nif err != nil {\n    log.Fatal(err)\n}\n\n// Or from JSON bytes\nschema, err := aperture.LoadSchemaFromJSON(jsonData)",{"id":552,"title":553,"titles":554,"content":555,"level":40},"/v1.0.3/guides/schema#validation","Validation",[538,548],"if err := schema.Validate(); err != nil {\n    log.Fatalf(\"invalid schema: %v\", err)\n}",{"id":557,"title":558,"titles":559,"content":34,"level":19},"/v1.0.3/guides/schema#schema-format","Schema Format",[538],{"id":561,"title":562,"titles":563,"content":564,"level":40},"/v1.0.3/guides/schema#yaml-example","YAML Example",[538,558],"# observability.yaml\n\nmetrics:\n  - signal: order.created\n    name: orders_total\n    type: counter\n    description: Total orders placed\n\n  - signal: request.done\n    name: request_duration_ms\n    type: histogram\n    value_key: duration\n\n  - signal: queue.changed\n    name: queue_depth\n    type: updowncounter\n    value_key: delta\n\ntraces:\n  - start: request.started\n    end: request.done\n    correlation_key: request_id\n    span_name: http-request\n    span_timeout: 5m\n\nlogs:\n  whitelist:\n    - order.created\n    - order.failed\n    - request.done\n\ncontext:\n  logs:\n    - user_id\n    - request_id\n  metrics:\n    - region\n  traces:\n    - user_id\n    - region\n\nstdout: false",{"id":566,"title":567,"titles":568,"content":569,"level":40},"/v1.0.3/guides/schema#json-example","JSON Example",[538,558],"{\n  \"metrics\": [\n    {\n      \"signal\": \"order.created\",\n      \"name\": \"orders_total\",\n      \"type\": \"counter\"\n    }\n  ],\n  \"traces\": [\n    {\n      \"start\": \"request.started\",\n      \"end\": \"request.done\",\n      \"correlation_key\": \"request_id\",\n      \"span_name\": \"http-request\"\n    }\n  ],\n  \"logs\": {\n    \"whitelist\": [\"order.created\", \"order.failed\"]\n  }\n}",{"id":571,"title":572,"titles":573,"content":574,"level":19},"/v1.0.3/guides/schema#applying-schemas","Applying Schemas",[538],"Apply schemas directly to aperture: ap, _ := aperture.New(cap, logProvider, meterProvider, traceProvider)\ndefer ap.Close()\n\nconfigBytes, _ := os.ReadFile(\"config.yaml\")\nschema, _ := aperture.LoadSchemaFromYAML(configBytes)\n\nif err := schema.Validate(); err != nil {\n    log.Fatal(err)\n}\n\nif err := ap.Apply(schema); err != nil {\n    log.Fatal(err)\n}",{"id":576,"title":577,"titles":578,"content":579,"level":19},"/v1.0.3/guides/schema#context-key-registration","Context Key Registration",[538],"If using context extraction, register keys before applying: type ctxKey string\nconst (\n    userIDKey  ctxKey = \"user_id\"\n    regionKey  ctxKey = \"region\"\n    requestKey ctxKey = \"request_id\"\n)\n\nap.RegisterContextKey(\"user_id\", userIDKey)\nap.RegisterContextKey(\"region\", regionKey)\nap.RegisterContextKey(\"request_id\", requestKey)\n\n// Now apply schema that references these keys\nap.Apply(schema)",{"id":581,"title":582,"titles":583,"content":584,"level":19},"/v1.0.3/guides/schema#hot-reload-with-flux","Hot-Reload with Flux",[538],"Integrate with flux for live configuration updates: // Create aperture once\nap, _ := aperture.New(cap, logProvider, meterProvider, traceProvider)\ndefer ap.Close()\n\n// Register context keys if needed\nap.RegisterContextKey(\"user_id\", userIDKey)\nap.RegisterContextKey(\"region\", regionKey)\n\n// Watch config file and apply changes\ncapacitor := flux.New[aperture.Schema](\n    file.New(\"observability.yaml\"),\n    func(_, schema aperture.Schema) error {\n        if err := schema.Validate(); err != nil {\n            return err\n        }\n        return ap.Apply(schema)\n    },\n)\ncapacitor.Start(ctx) Changes to observability.yaml are applied live without restart. The Apply() method atomically swaps the configuration.",{"id":586,"title":148,"titles":587,"content":588,"level":19},"/v1.0.3/guides/schema#name-based-matching",[538],"Schema configuration uses string names that match at runtime: // Define signals and keys in code\norderCreated := capitan.NewSignal(\"order.created\", \"Order created\")\norderID := capitan.NewStringKey(\"order_id\")\n\n// Schema references them by name\nschema := aperture.Schema{\n    Metrics: []aperture.MetricSchema{\n        {Signal: \"order.created\", Name: \"orders_total\", Type: \"counter\"},\n    },\n}\n\n// At runtime, event.Signal().Name() matches \"order.created\" This decouples configuration from Go types, enabling hot-reload without recompilation.",{"id":590,"title":591,"titles":592,"content":34,"level":19},"/v1.0.3/guides/schema#schema-reference","Schema Reference",[538],{"id":594,"title":138,"titles":595,"content":596,"level":40},"/v1.0.3/guides/schema#metrics",[538,591],"FieldRequiredDescriptionsignalYesSignal name to matchnameYesOTEL metric nametypeNocounter (default), gauge, histogram, updowncountervalue_keyFor non-countersField key name for numeric valuedescriptionNoMetric description",{"id":598,"title":143,"titles":599,"content":600,"level":40},"/v1.0.3/guides/schema#traces",[538,591],"FieldRequiredDescriptionstartYesSignal name that begins the spanendYesSignal name that completes the spancorrelation_keyYesField key name to match start/endspan_nameNoSpan name (defaults to start signal name)span_timeoutNoMax wait for end event (default: 5m)",{"id":602,"title":133,"titles":603,"content":604,"level":40},"/v1.0.3/guides/schema#logs",[538,591],"FieldDescriptionwhitelistSignal names to log (empty = log all)",{"id":606,"title":607,"titles":608,"content":609,"level":40},"/v1.0.3/guides/schema#context","Context",[538,591],"FieldDescriptionlogsContext key names for log attributesmetricsContext key names for metric dimensionstracesContext key names for span attributes",{"id":611,"title":612,"titles":613,"content":614,"level":40},"/v1.0.3/guides/schema#root-options","Root Options",[538,591],"FieldDescriptionstdoutEnable stdout logging (boolean)",{"id":616,"title":260,"titles":617,"content":618,"level":19},"/v1.0.3/guides/schema#error-handling",[538],"Validation catches structural issues: err := schema.Validate()\n// Possible errors:\n// - \"metric config missing signal\"\n// - \"metric config missing name\"\n// - \"trace config missing correlation_key\" Runtime matching is silent: Unknown signal names: events don't match, no metrics/traces createdUnknown key names: values not extractedThis enables gradual rollout of new signals",{"id":620,"title":226,"titles":621,"content":622,"level":19},"/v1.0.3/guides/schema#custom-type-handling",[538],"Custom field types are automatically JSON serialized to string attributes. No registration required: type OrderInfo struct {\n    ID     string  `json:\"id\"`\n    Total  float64 `json:\"total\"`\n    Secret string  `json:\"-\"`  // Excluded\n}\n\norderKey := capitan.NewKey[OrderInfo](\"order\", \"Order details\")\n\n// Automatically serialized as JSON\ncap.Emit(ctx, sig, orderKey.Field(OrderInfo{ID: \"ORD-123\", Total: 99.99}))\n// Attribute: order=\"{\\\"id\\\":\\\"ORD-123\\\",\\\"total\\\":99.99}\" Use JSON struct tags to control serialization. html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sfm-E, html code.shiki .sfm-E{--shiki-default:var(--shiki-variable)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",{"id":624,"title":625,"titles":626,"content":627,"level":9},"/v1.0.3/guides/testing","Testing",[],"Test your aperture configurations",{"id":629,"title":630,"titles":631,"content":632,"level":9},"/v1.0.3/guides/testing#testing-guide","Testing Guide",[],"Test aperture configurations using the aperture/testing package.",{"id":634,"title":61,"titles":635,"content":636,"level":19},"/v1.0.3/guides/testing#installation",[630],"import apertesting \"github.com/zoobz-io/aperture/testing\"",{"id":638,"title":639,"titles":640,"content":641,"level":19},"/v1.0.3/guides/testing#mock-logger-provider","Mock Logger Provider",[630],"Capture logs without external collectors: func TestLogging(t *testing.T) {\n    ctx := context.Background()\n\n    cap := capitan.New()\n    defer cap.Shutdown()\n\n    sig := capitan.NewSignal(\"test.event\", \"Test event\")\n\n    // Use mock logger provider\n    mockLog := apertesting.NewMockLoggerProvider()\n    ap, err := aperture.New(cap, mockLog, noop.NewMeterProvider(), tracenoop.NewTracerProvider())\n    if err != nil {\n        t.Fatal(err)\n    }\n    defer ap.Close()\n\n    // Emit events\n    cap.Emit(ctx, sig)\n    cap.Emit(ctx, sig)\n    cap.Shutdown()\n\n    // Verify logs captured\n    records := mockLog.Capture().Records()\n    if len(records) != 2 {\n        t.Errorf(\"expected 2 records, got %d\", len(records))\n    }\n}",{"id":643,"title":644,"titles":645,"content":646,"level":19},"/v1.0.3/guides/testing#log-capture","Log Capture",[630],"Access captured log records: mockLog := apertesting.NewMockLoggerProvider()\n\n// After emissions\ncap.Shutdown()\n\ncapture := mockLog.Capture()\n\n// Count records\nif capture.Count() != 3 {\n    t.Error(\"expected 3 records\")\n}\n\n// Get all records\nrecords := capture.Records()\nfor _, r := range records {\n    // Inspect log.Record\n}\n\n// Reset for next test\ncapture.Reset()",{"id":648,"title":649,"titles":650,"content":651,"level":19},"/v1.0.3/guides/testing#wait-for-async-operations","Wait for Async Operations",[630],"Wait for expected record count: capture := mockLog.Capture()\n\n// Emit asynchronously\ngo func() {\n    for i := 0; i \u003C 10; i++ {\n        cap.Emit(ctx, sig)\n    }\n}()\n\n// Wait up to 1 second for 10 records\nif !capture.WaitForCount(10, time.Second) {\n    t.Error(\"timeout waiting for records\")\n}",{"id":653,"title":654,"titles":655,"content":656,"level":19},"/v1.0.3/guides/testing#event-capture","Event Capture",[630],"Capture capitan events directly: func TestEventCapture(t *testing.T) {\n    ctx := context.Background()\n\n    cap := capitan.New()\n    defer cap.Shutdown()\n\n    sig := capitan.NewSignal(\"test.event\", \"Test event\")\n    keyField := capitan.NewStringKey(\"key\")\n\n    // Hook event capture\n    capture := apertesting.NewEventCapture()\n    cap.Hook(sig, capture.Handler())\n\n    // Emit events\n    cap.Emit(ctx, sig, keyField.Field(\"value1\"))\n    cap.Emit(ctx, sig, keyField.Field(\"value2\"))\n    cap.Shutdown()\n\n    // Verify captured events\n    events := capture.Events()\n    if len(events) != 2 {\n        t.Errorf(\"expected 2 events, got %d\", len(events))\n    }\n\n    // Inspect event details\n    if events[0].Signal != sig {\n        t.Error(\"wrong signal\")\n    }\n}",{"id":658,"title":659,"titles":660,"content":661,"level":19},"/v1.0.3/guides/testing#testing-with-real-providers","Testing with Real Providers",[630],"For integration tests against actual OTEL collectors: func TestWithCollector(t *testing.T) {\n    ctx := context.Background()\n\n    // Create real OTLP providers (requires running collector)\n    pvs, err := apertesting.TestProviders(ctx, \"test-service\", \"v1.0.0\", \"localhost:4318\")\n    if err != nil {\n        t.Skipf(\"OTLP collector not available: %v\", err)\n    }\n    defer pvs.Shutdown(ctx)\n\n    cap := capitan.New()\n    defer cap.Shutdown()\n\n    ap, err := aperture.New(cap, pvs.Log, pvs.Meter, pvs.Trace)\n    if err != nil {\n        t.Fatal(err)\n    }\n    defer ap.Close()\n\n    // Test with real providers\n    sig := capitan.NewSignal(\"integration.test\", \"Integration test\")\n    cap.Emit(ctx, sig)\n}",{"id":663,"title":664,"titles":665,"content":666,"level":19},"/v1.0.3/guides/testing#testing-whitelist-filtering","Testing Whitelist Filtering",[630],"Verify log filtering works: func TestWhitelistFiltering(t *testing.T) {\n    ctx := context.Background()\n\n    cap := capitan.New()\n    defer cap.Shutdown()\n\n    sigAllowed := capitan.NewSignal(\"allowed\", \"Allowed signal\")\n    sigBlocked := capitan.NewSignal(\"blocked\", \"Blocked signal\")\n\n    schema := aperture.Schema{\n        Logs: &aperture.LogSchema{\n            Whitelist: []string{\"allowed\"},\n        },\n    }\n\n    mockLog := apertesting.NewMockLoggerProvider()\n    ap, _ := aperture.New(cap, mockLog, noop.NewMeterProvider(), tracenoop.NewTracerProvider())\n    ap.Apply(schema)\n    defer ap.Close()\n\n    cap.Emit(ctx, sigAllowed)\n    cap.Emit(ctx, sigBlocked)\n    cap.Shutdown()\n\n    records := mockLog.Capture().Records()\n    if len(records) != 1 {\n        t.Errorf(\"expected 1 record (whitelist filtering), got %d\", len(records))\n    }\n}",{"id":668,"title":669,"titles":670,"content":671,"level":19},"/v1.0.3/guides/testing#testing-context-extraction","Testing Context Extraction",[630],"Verify context values are extracted: func TestContextExtraction(t *testing.T) {\n    ctx := context.Background()\n\n    type ctxKey string\n    const userKey ctxKey = \"user_id\"\n\n    cap := capitan.New()\n    defer cap.Shutdown()\n\n    sig := capitan.NewSignal(\"test\", \"Test signal\")\n\n    mockLog := apertesting.NewMockLoggerProvider()\n    ap, _ := aperture.New(cap, mockLog, noop.NewMeterProvider(), tracenoop.NewTracerProvider())\n\n    ap.RegisterContextKey(\"user_id\", userKey)\n\n    schema := aperture.Schema{\n        Context: &aperture.ContextSchema{\n            Logs: []string{\"user_id\"},\n        },\n    }\n    ap.Apply(schema)\n    defer ap.Close()\n\n    ctx = context.WithValue(ctx, userKey, \"user-123\")\n    cap.Emit(ctx, sig)\n    cap.Shutdown()\n\n    records := mockLog.Capture().Records()\n    // Inspect records for user_id attribute\n}",{"id":673,"title":674,"titles":675,"content":676,"level":19},"/v1.0.3/guides/testing#testing-trace-correlation","Testing Trace Correlation",[630],"Verify span creation and correlation: func TestTraceCorrelation(t *testing.T) {\n    ctx := context.Background()\n\n    cap := capitan.New()\n    defer cap.Shutdown()\n\n    reqStarted := capitan.NewSignal(\"req.started\", \"Started\")\n    reqDone := capitan.NewSignal(\"req.done\", \"Done\")\n    requestID := capitan.NewStringKey(\"request_id\")\n\n    schema := aperture.Schema{\n        Traces: []aperture.TraceSchema{\n            {\n                Start:          \"req.started\",\n                End:            \"req.done\",\n                CorrelationKey: \"request_id\",\n                SpanName:       \"test_span\",\n            },\n        },\n    }\n\n    mockLog := apertesting.NewMockLoggerProvider()\n    ap, _ := aperture.New(cap, mockLog, noop.NewMeterProvider(), tracenoop.NewTracerProvider())\n    ap.Apply(schema)\n    defer ap.Close()\n\n    // Emit correlated events\n    cap.Emit(ctx, reqStarted, requestID.Field(\"REQ-001\"))\n    cap.Emit(ctx, reqDone, requestID.Field(\"REQ-001\"))\n    cap.Shutdown()\n\n    // Verify no panics, logs captured\n    // For actual span verification, use a span exporter\n}",{"id":678,"title":679,"titles":680,"content":681,"level":19},"/v1.0.3/guides/testing#noop-providers","Noop Providers",[630],"Use OTEL noop providers when testing components that don't need full providers: import (\n    \"go.opentelemetry.io/otel/metric/noop\"\n    tracenoop \"go.opentelemetry.io/otel/trace/noop\"\n)\n\n// Only test logging behavior\nmockLog := apertesting.NewMockLoggerProvider()\nap, _ := aperture.New(cap, mockLog, noop.NewMeterProvider(), tracenoop.NewTracerProvider())",{"id":683,"title":684,"titles":685,"content":686,"level":19},"/v1.0.3/guides/testing#benchmarking","Benchmarking",[630],"Run benchmarks: go test -bench=. ./testing/benchmarks/ Example benchmark output: BenchmarkEmit_NoConfig-8                 5000000           234 ns/op          48 B/op          1 allocs/op\nBenchmarkEmit_WithMetricsCounter-8       3000000           456 ns/op          96 B/op          2 allocs/op\nBenchmarkEmit_WithLogs-8                 2000000           612 ns/op         144 B/op          3 allocs/op html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sfm-E, html code.shiki .sfm-E{--shiki-default:var(--shiki-variable)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}",{"id":688,"title":689,"titles":690,"content":691,"level":9},"/v1.0.3/cookbook/http-server","HTTP Server Observability",[],"Instrument an HTTP server with aperture",{"id":693,"title":689,"titles":694,"content":695,"level":9},"/v1.0.3/cookbook/http-server#http-server-observability",[],"Complete example of instrumenting an HTTP server with aperture.",{"id":697,"title":698,"titles":699,"content":700,"level":19},"/v1.0.3/cookbook/http-server#signals-and-keys","Signals and Keys",[689],"package signals\n\nimport \"github.com/zoobz-io/capitan\"\n\n// HTTP request lifecycle\nvar (\n    HTTPRequestReceived  = capitan.NewSignal(\"http.request.received\", \"HTTP request received\")\n    HTTPRequestCompleted = capitan.NewSignal(\"http.request.completed\", \"HTTP request completed\")\n)\n\n// Field keys\nvar (\n    RequestID  = capitan.NewStringKey(\"request_id\")\n    Method     = capitan.NewStringKey(\"method\")\n    Path       = capitan.NewStringKey(\"path\")\n    StatusCode = capitan.NewIntKey(\"status\")\n    Duration   = capitan.NewDurationKey(\"duration\")\n    UserID     = capitan.NewStringKey(\"user_id\")\n)",{"id":702,"title":496,"titles":703,"content":704,"level":19},"/v1.0.3/cookbook/http-server#configuration",[689],"package main\n\nimport \"github.com/zoobz-io/aperture\"\n\nfunc observabilitySchema() aperture.Schema {\n    return aperture.Schema{\n        Metrics: []aperture.MetricSchema{\n            // Count requests\n            {\n                Signal:      \"http.request.completed\",\n                Name:        \"http_requests_total\",\n                Type:        \"counter\",\n                Description: \"Total HTTP requests\",\n            },\n            // Latency histogram\n            {\n                Signal:      \"http.request.completed\",\n                Name:        \"http_request_duration_ms\",\n                Type:        \"histogram\",\n                ValueKey:    \"duration\",\n                Description: \"HTTP request latency distribution\",\n            },\n        },\n        Traces: []aperture.TraceSchema{\n            {\n                Start:          \"http.request.received\",\n                End:            \"http.request.completed\",\n                CorrelationKey: \"request_id\",\n                SpanName:       \"http_request\",\n            },\n        },\n        Logs: &aperture.LogSchema{\n            Whitelist: []string{\n                \"http.request.received\",\n                \"http.request.completed\",\n            },\n        },\n    }\n}",{"id":706,"title":707,"titles":708,"content":709,"level":19},"/v1.0.3/cookbook/http-server#middleware","Middleware",[689],"package middleware\n\nimport (\n    \"context\"\n    \"net/http\"\n    \"time\"\n\n    \"github.com/google/uuid\"\n    \"github.com/zoobz-io/capitan\"\n    \"myapp/signals\"\n)\n\ntype ctxKey string\n\nconst requestIDKey ctxKey = \"request_id\"\n\nfunc Observability(cap *capitan.Capitan) func(http.Handler) http.Handler {\n    return func(next http.Handler) http.Handler {\n        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n            ctx := r.Context()\n            start := time.Now()\n\n            // Generate request ID\n            reqID := r.Header.Get(\"X-Request-ID\")\n            if reqID == \"\" {\n                reqID = uuid.New().String()\n            }\n            ctx = context.WithValue(ctx, requestIDKey, reqID)\n\n            // Emit request received\n            cap.Emit(ctx, signals.HTTPRequestReceived,\n                signals.RequestID.Field(reqID),\n                signals.Method.Field(r.Method),\n                signals.Path.Field(r.URL.Path),\n            )\n\n            // Wrap response writer to capture status\n            wrapped := &statusWriter{ResponseWriter: w, status: http.StatusOK}\n\n            // Process request\n            next.ServeHTTP(wrapped, r.WithContext(ctx))\n\n            // Emit request completed\n            cap.Emit(ctx, signals.HTTPRequestCompleted,\n                signals.RequestID.Field(reqID),\n                signals.Method.Field(r.Method),\n                signals.Path.Field(r.URL.Path),\n                signals.StatusCode.Field(wrapped.status),\n                signals.Duration.Field(time.Since(start)),\n            )\n        })\n    }\n}\n\ntype statusWriter struct {\n    http.ResponseWriter\n    status int\n}\n\nfunc (w *statusWriter) WriteHeader(status int) {\n    w.status = status\n    w.ResponseWriter.WriteHeader(status)\n}",{"id":711,"title":712,"titles":713,"content":714,"level":19},"/v1.0.3/cookbook/http-server#main-application","Main Application",[689],"package main\n\nimport (\n    \"context\"\n    \"log\"\n    \"net/http\"\n    \"time\"\n\n    \"github.com/zoobz-io/aperture\"\n    \"github.com/zoobz-io/capitan\"\n    \"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp\"\n    \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp\"\n    \"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp\"\n    sdklog \"go.opentelemetry.io/otel/sdk/log\"\n    sdkmetric \"go.opentelemetry.io/otel/sdk/metric\"\n    \"go.opentelemetry.io/otel/sdk/resource\"\n    sdktrace \"go.opentelemetry.io/otel/sdk/trace\"\n    semconv \"go.opentelemetry.io/otel/semconv/v1.28.0\"\n    \"myapp/middleware\"\n)\n\nfunc main() {\n    ctx := context.Background()\n\n    // Create resource\n    res, _ := resource.Merge(\n        resource.Default(),\n        resource.NewSchemaless(\n            semconv.ServiceName(\"my-api\"),\n            semconv.ServiceVersion(\"v1.0.0\"),\n        ),\n    )\n\n    // Create providers\n    logExporter, _ := otlploghttp.New(ctx, otlploghttp.WithEndpoint(\"localhost:4318\"), otlploghttp.WithInsecure())\n    logProvider := sdklog.NewLoggerProvider(\n        sdklog.WithResource(res),\n        sdklog.WithProcessor(sdklog.NewBatchProcessor(logExporter)),\n    )\n    defer logProvider.Shutdown(ctx)\n\n    metricExporter, _ := otlpmetrichttp.New(ctx, otlpmetrichttp.WithEndpoint(\"localhost:4318\"), otlpmetrichttp.WithInsecure())\n    meterProvider := sdkmetric.NewMeterProvider(\n        sdkmetric.WithResource(res),\n        sdkmetric.WithReader(sdkmetric.NewPeriodicReader(metricExporter, sdkmetric.WithInterval(60*time.Second))),\n    )\n    defer meterProvider.Shutdown(ctx)\n\n    traceExporter, _ := otlptracehttp.New(ctx, otlptracehttp.WithEndpoint(\"localhost:4318\"), otlptracehttp.WithInsecure())\n    traceProvider := sdktrace.NewTracerProvider(\n        sdktrace.WithResource(res),\n        sdktrace.WithSpanProcessor(sdktrace.NewBatchSpanProcessor(traceExporter)),\n        sdktrace.WithSampler(sdktrace.AlwaysSample()),\n    )\n    defer traceProvider.Shutdown(ctx)\n\n    // Create capitan and aperture\n    cap := capitan.Default()\n    defer cap.Shutdown()\n\n    ap, err := aperture.New(cap, logProvider, meterProvider, traceProvider)\n    if err != nil {\n        log.Fatal(err)\n    }\n    defer ap.Close()\n\n    // Apply observability schema\n    ap.Apply(observabilitySchema())\n\n    // Create HTTP server with observability middleware\n    mux := http.NewServeMux()\n    mux.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n        w.Write([]byte(\"Hello, World!\"))\n    })\n\n    handler := middleware.Observability(cap)(mux)\n\n    log.Println(\"Starting server on :8080\")\n    http.ListenAndServe(\":8080\", handler)\n}",{"id":716,"title":717,"titles":718,"content":34,"level":19},"/v1.0.3/cookbook/http-server#resulting-telemetry","Resulting Telemetry",[689],{"id":720,"title":138,"titles":721,"content":722,"level":40},"/v1.0.3/cookbook/http-server#metrics",[689,717],"http_requests_total{method=\"GET\", path=\"/\", status=\"200\"} 42\nhttp_request_duration_ms_bucket{method=\"GET\", path=\"/\", le=\"10\"} 5\nhttp_request_duration_ms_bucket{method=\"GET\", path=\"/\", le=\"50\"} 35\nhttp_request_duration_ms_bucket{method=\"GET\", path=\"/\", le=\"100\"} 41\nhttp_request_duration_ms_bucket{method=\"GET\", path=\"/\", le=\"+Inf\"} 42",{"id":724,"title":133,"titles":725,"content":726,"level":40},"/v1.0.3/cookbook/http-server#logs",[689,717],"{\n  \"timestamp\": \"2025-01-15T10:30:00Z\",\n  \"severity\": \"INFO\",\n  \"body\": \"HTTP request received\",\n  \"attributes\": {\n    \"capitan.signal\": \"http.request.received\",\n    \"request_id\": \"abc-123\",\n    \"method\": \"GET\",\n    \"path\": \"/\"\n  }\n}",{"id":728,"title":143,"titles":729,"content":730,"level":40},"/v1.0.3/cookbook/http-server#traces",[689,717],"Span: http_request\n  TraceID: abc123...\n  Duration: 45ms\n  Attributes:\n    - request_id: abc-123\n    - method: GET\n    - path: /\n    - status: 200\n    - duration: 45000000",{"id":732,"title":733,"titles":734,"content":735,"level":19},"/v1.0.3/cookbook/http-server#with-context-extraction","With Context Extraction",[689],"Add user ID from authentication: type ctxKey string\nconst userIDKey ctxKey = \"user_id\"\n\nap.RegisterContextKey(\"user_id\", userIDKey)\n\nschema := observabilitySchema()\nschema.Context = &aperture.ContextSchema{\n    Logs:   []string{\"user_id\"},\n    Traces: []string{\"user_id\"},\n}\nap.Apply(schema) Then in auth middleware: ctx = context.WithValue(ctx, userIDKey, authenticatedUserID) All logs and traces automatically include user_id. html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sfm-E, html code.shiki .sfm-E{--shiki-default:var(--shiki-variable)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",{"id":737,"title":738,"titles":739,"content":740,"level":9},"/v1.0.3/cookbook/background-workers","Background Worker Observability",[],"Instrument background workers and job queues",{"id":742,"title":738,"titles":743,"content":744,"level":9},"/v1.0.3/cookbook/background-workers#background-worker-observability",[],"Instrument background job processing with aperture.",{"id":746,"title":698,"titles":747,"content":748,"level":19},"/v1.0.3/cookbook/background-workers#signals-and-keys",[738],"package signals\n\nimport \"github.com/zoobz-io/capitan\"\n\n// Job lifecycle\nvar (\n    JobStarted   = capitan.NewSignal(\"job.started\", \"Job started processing\")\n    JobCompleted = capitan.NewSignal(\"job.completed\", \"Job completed successfully\")\n    JobFailed    = capitan.NewSignal(\"job.failed\", \"Job failed\")\n    JobRetrying  = capitan.NewSignal(\"job.retrying\", \"Job retry scheduled\")\n)\n\n// Queue events\nvar (\n    QueueEnqueue = capitan.NewSignal(\"queue.enqueue\", \"Job enqueued\")\n    QueueDepth   = capitan.NewSignal(\"queue.depth\", \"Queue depth measurement\")\n)\n\n// Field keys\nvar (\n    JobID       = capitan.NewStringKey(\"job_id\")\n    JobType     = capitan.NewStringKey(\"job_type\")\n    QueueName   = capitan.NewStringKey(\"queue\")\n    Duration    = capitan.NewDurationKey(\"duration\")\n    ErrorMsg    = capitan.NewStringKey(\"error\")\n    RetryCount  = capitan.NewIntKey(\"retry_count\")\n    Depth       = capitan.NewInt64Key(\"depth\")\n)",{"id":750,"title":496,"titles":751,"content":752,"level":19},"/v1.0.3/cookbook/background-workers#configuration",[738],"package main\n\nimport \"github.com/zoobz-io/aperture\"\n\nfunc workerSchema() aperture.Schema {\n    return aperture.Schema{\n        Metrics: []aperture.MetricSchema{\n            // Jobs processed\n            {\n                Signal:      \"job.completed\",\n                Name:        \"jobs_completed_total\",\n                Type:        \"counter\",\n                Description: \"Total jobs completed successfully\",\n            },\n            // Jobs failed\n            {\n                Signal:      \"job.failed\",\n                Name:        \"jobs_failed_total\",\n                Type:        \"counter\",\n                Description: \"Total jobs failed\",\n            },\n            // Job duration\n            {\n                Signal:      \"job.completed\",\n                Name:        \"job_duration_ms\",\n                Type:        \"histogram\",\n                ValueKey:    \"duration\",\n                Description: \"Job processing duration\",\n            },\n            // Queue depth gauge\n            {\n                Signal:   \"queue.depth\",\n                Name:     \"queue_depth\",\n                Type:     \"gauge\",\n                ValueKey: \"depth\",\n            },\n        },\n        Traces: []aperture.TraceSchema{\n            // Trace job processing\n            {\n                Start:          \"job.started\",\n                End:            \"job.completed\",\n                CorrelationKey: \"job_id\",\n                SpanName:       \"job_processing\",\n            },\n        },\n        Logs: &aperture.LogSchema{\n            Whitelist: []string{\n                \"job.started\",\n                \"job.completed\",\n                \"job.failed\",\n                \"job.retrying\",\n            },\n        },\n    }\n}",{"id":754,"title":755,"titles":756,"content":757,"level":19},"/v1.0.3/cookbook/background-workers#worker-implementation","Worker Implementation",[738],"package worker\n\nimport (\n    \"context\"\n    \"time\"\n\n    \"github.com/zoobz-io/capitan\"\n    \"myapp/signals\"\n)\n\ntype Job struct {\n    ID   string\n    Type string\n    Data any\n}\n\ntype Worker struct {\n    cap       *capitan.Capitan\n    queueName string\n}\n\nfunc NewWorker(cap *capitan.Capitan, queueName string) *Worker {\n    return &Worker{cap: cap, queueName: queueName}\n}\n\nfunc (w *Worker) Process(ctx context.Context, job Job) error {\n    start := time.Now()\n\n    // Emit job started\n    w.cap.Emit(ctx, signals.JobStarted,\n        signals.JobID.Field(job.ID),\n        signals.JobType.Field(job.Type),\n        signals.QueueName.Field(w.queueName),\n    )\n\n    // Process the job\n    err := w.processJob(ctx, job)\n\n    duration := time.Since(start)\n\n    if err != nil {\n        // Emit job failed\n        w.cap.Emit(ctx, signals.JobFailed,\n            signals.JobID.Field(job.ID),\n            signals.JobType.Field(job.Type),\n            signals.QueueName.Field(w.queueName),\n            signals.Duration.Field(duration),\n            signals.ErrorMsg.Field(err.Error()),\n        )\n        return err\n    }\n\n    // Emit job completed\n    w.cap.Emit(ctx, signals.JobCompleted,\n        signals.JobID.Field(job.ID),\n        signals.JobType.Field(job.Type),\n        signals.QueueName.Field(w.queueName),\n        signals.Duration.Field(duration),\n    )\n\n    return nil\n}\n\nfunc (w *Worker) processJob(ctx context.Context, job Job) error {\n    // Job-specific processing\n    switch job.Type {\n    case \"email\":\n        return w.sendEmail(ctx, job)\n    case \"report\":\n        return w.generateReport(ctx, job)\n    default:\n        return nil\n    }\n}",{"id":759,"title":760,"titles":761,"content":762,"level":19},"/v1.0.3/cookbook/background-workers#queue-with-depth-monitoring","Queue with Depth Monitoring",[738],"package queue\n\nimport (\n    \"context\"\n    \"sync\"\n    \"time\"\n\n    \"github.com/zoobz-io/capitan\"\n    \"myapp/signals\"\n    \"myapp/worker\"\n)\n\ntype Queue struct {\n    name  string\n    cap   *capitan.Capitan\n    jobs  chan worker.Job\n    mu    sync.RWMutex\n    depth int64\n}\n\nfunc NewQueue(cap *capitan.Capitan, name string, size int) *Queue {\n    q := &Queue{\n        name: name,\n        cap:  cap,\n        jobs: make(chan worker.Job, size),\n    }\n\n    // Start depth monitoring\n    go q.monitorDepth(context.Background())\n\n    return q\n}\n\nfunc (q *Queue) Enqueue(ctx context.Context, job worker.Job) {\n    q.mu.Lock()\n    q.depth++\n    q.mu.Unlock()\n\n    q.cap.Emit(ctx, signals.QueueEnqueue,\n        signals.JobID.Field(job.ID),\n        signals.JobType.Field(job.Type),\n        signals.QueueName.Field(q.name),\n    )\n\n    q.jobs \u003C- job\n}\n\nfunc (q *Queue) Dequeue() worker.Job {\n    job := \u003C-q.jobs\n\n    q.mu.Lock()\n    q.depth--\n    q.mu.Unlock()\n\n    return job\n}\n\nfunc (q *Queue) monitorDepth(ctx context.Context) {\n    ticker := time.NewTicker(10 * time.Second)\n    defer ticker.Stop()\n\n    for {\n        select {\n        case \u003C-ctx.Done():\n            return\n        case \u003C-ticker.C:\n            q.mu.RLock()\n            depth := q.depth\n            q.mu.RUnlock()\n\n            q.cap.Emit(ctx, signals.QueueDepth,\n                signals.QueueName.Field(q.name),\n                signals.Depth.Field(depth),\n            )\n        }\n    }\n}",{"id":764,"title":765,"titles":766,"content":767,"level":19},"/v1.0.3/cookbook/background-workers#retry-handler","Retry Handler",[738],"package worker\n\nimport (\n    \"context\"\n    \"time\"\n\n    \"myapp/signals\"\n)\n\nfunc (w *Worker) ProcessWithRetry(ctx context.Context, job Job, maxRetries int) error {\n    var lastErr error\n\n    for attempt := 0; attempt \u003C= maxRetries; attempt++ {\n        err := w.Process(ctx, job)\n        if err == nil {\n            return nil\n        }\n\n        lastErr = err\n\n        if attempt \u003C maxRetries {\n            // Emit retry signal\n            w.cap.Emit(ctx, signals.JobRetrying,\n                signals.JobID.Field(job.ID),\n                signals.JobType.Field(job.Type),\n                signals.QueueName.Field(w.queueName),\n                signals.RetryCount.Field(attempt+1),\n                signals.ErrorMsg.Field(err.Error()),\n            )\n\n            // Exponential backoff\n            time.Sleep(time.Duration(1\u003C\u003Cattempt) * time.Second)\n        }\n    }\n\n    return lastErr\n}",{"id":769,"title":712,"titles":770,"content":771,"level":19},"/v1.0.3/cookbook/background-workers#main-application",[738],"package main\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"log\"\n\n    \"github.com/zoobz-io/aperture\"\n    \"github.com/zoobz-io/capitan\"\n    \"myapp/queue\"\n    \"myapp/worker\"\n)\n\nfunc main() {\n    ctx := context.Background()\n\n    // Setup providers (as in HTTP example)\n    // ...\n\n    cap := capitan.Default()\n    defer cap.Shutdown()\n\n    ap, _ := aperture.New(cap, logProvider, meterProvider, traceProvider)\n    ap.Apply(workerSchema())\n    defer ap.Close()\n\n    // Create queue and workers\n    q := queue.NewQueue(cap, \"default\", 1000)\n\n    // Start workers\n    for i := 0; i \u003C 10; i++ {\n        w := worker.NewWorker(cap, \"default\")\n        go func() {\n            for {\n                job := q.Dequeue()\n                _ = w.ProcessWithRetry(ctx, job, 3)\n            }\n        }()\n    }\n\n    // Enqueue jobs\n    for i := 0; i \u003C 100; i++ {\n        q.Enqueue(ctx, worker.Job{\n            ID:   fmt.Sprintf(\"job-%d\", i),\n            Type: \"email\",\n            Data: nil,\n        })\n    }\n\n    select {} // Run forever\n}",{"id":773,"title":717,"titles":774,"content":34,"level":19},"/v1.0.3/cookbook/background-workers#resulting-telemetry",[738],{"id":776,"title":138,"titles":777,"content":778,"level":40},"/v1.0.3/cookbook/background-workers#metrics",[738,717],"jobs_completed_total{job_type=\"email\", queue=\"default\"} 95\njobs_failed_total{job_type=\"email\", queue=\"default\"} 5\njob_duration_ms_bucket{job_type=\"email\", queue=\"default\", le=\"100\"} 50\njob_duration_ms_bucket{job_type=\"email\", queue=\"default\", le=\"500\"} 90\nqueue_depth{queue=\"default\"} 23",{"id":780,"title":133,"titles":781,"content":782,"level":40},"/v1.0.3/cookbook/background-workers#logs",[738,717],"{\n  \"timestamp\": \"2025-01-15T10:30:00Z\",\n  \"severity\": \"ERROR\",\n  \"body\": \"Job failed\",\n  \"attributes\": {\n    \"capitan.signal\": \"job.failed\",\n    \"job_id\": \"job-42\",\n    \"job_type\": \"email\",\n    \"queue\": \"default\",\n    \"error\": \"connection refused\"\n  }\n}",{"id":784,"title":143,"titles":785,"content":786,"level":40},"/v1.0.3/cookbook/background-workers#traces",[738,717],"Span: job_processing\n  TraceID: def456...\n  Duration: 234ms\n  Attributes:\n    - job_id: job-42\n    - job_type: email\n    - queue: default html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}",{"id":788,"title":789,"titles":790,"content":791,"level":9},"/v1.0.3/reference/api","API Reference",[],"Complete API documentation",{"id":793,"title":789,"titles":794,"content":34,"level":9},"/v1.0.3/reference/api#api-reference",[],{"id":796,"title":193,"titles":797,"content":34,"level":19},"/v1.0.3/reference/api#aperture",[789],{"id":799,"title":800,"titles":801,"content":802,"level":40},"/v1.0.3/reference/api#constructor","Constructor",[789,193],"func New(\n    c *capitan.Capitan,\n    logProvider log.LoggerProvider,\n    meterProvider metric.MeterProvider,\n    traceProvider trace.TracerProvider,\n) (*Aperture, error) Creates a new Aperture instance that observes all capitan events. Parameters: c - Capitan instance to observelogProvider - OTEL log provider (required)meterProvider - OTEL meter provider (required)traceProvider - OTEL trace provider (required) Returns: *Aperture - The aperture instanceerror - Initialization errors Example: ap, err := aperture.New(cap, logProvider, meterProvider, traceProvider)\nif err != nil {\n    log.Fatal(err)\n}\ndefer ap.Close()",{"id":804,"title":805,"titles":806,"content":34,"level":40},"/v1.0.3/reference/api#methods","Methods",[789,193],{"id":808,"title":809,"titles":810,"content":811,"level":812},"/v1.0.3/reference/api#apply","Apply",[789,193,805],"func (s *Aperture) Apply(schema Schema) error Applies or updates the aperture configuration atomically. Use this for initial configuration and hot-reload scenarios. Parameters: schema - Configuration schema (see Schema) Returns: error - Schema validation errors Example: schema := aperture.Schema{\n    Metrics: []aperture.MetricSchema{\n        {Signal: \"order.created\", Name: \"orders_total\", Type: \"counter\"},\n    },\n}\nerr := ap.Apply(schema) Hot-reload with flux: capacitor := flux.New[aperture.Schema](\n    file.New(\"config.yaml\"),\n    func(_, curr aperture.Schema) error {\n        return ap.Apply(curr)\n    },\n)\ncapacitor.Start(ctx)",4,{"id":814,"title":815,"titles":816,"content":817,"level":812},"/v1.0.3/reference/api#registercontextkey","RegisterContextKey",[789,193,805],"func (s *Aperture) RegisterContextKey(name string, key any) Registers a context key for extraction. Call this before Apply() if your schema uses context extraction. Parameters: name - Name to reference in schema (e.g., \"user_id\")key - Actual Go context key type Example: type ctxKey string\nconst userIDKey ctxKey = \"user_id\"\n\nap.RegisterContextKey(\"user_id\", userIDKey)\n\nschema := aperture.Schema{\n    Context: &aperture.ContextSchema{\n        Logs: []string{\"user_id\"},\n    },\n}\nap.Apply(schema)",{"id":819,"title":820,"titles":821,"content":822,"level":812},"/v1.0.3/reference/api#logger","Logger",[789,193,805],"func (s *Aperture) Logger(name string) log.Logger Returns an OTEL logger with the given name.",{"id":824,"title":825,"titles":826,"content":827,"level":812},"/v1.0.3/reference/api#meter","Meter",[789,193,805],"func (s *Aperture) Meter(name string) metric.Meter Returns an OTEL meter with the given name.",{"id":829,"title":830,"titles":831,"content":832,"level":812},"/v1.0.3/reference/api#tracer","Tracer",[789,193,805],"func (s *Aperture) Tracer(name string) trace.Tracer Returns an OTEL tracer with the given name.",{"id":834,"title":835,"titles":836,"content":837,"level":812},"/v1.0.3/reference/api#close","Close",[789,193,805],"func (s *Aperture) Close() Stops observing capitan events. Does NOT shutdown providers.",{"id":839,"title":198,"titles":840,"content":841,"level":19},"/v1.0.3/reference/api#schema",[789],"Schema is the configuration format for aperture. It can be defined in Go, YAML, or JSON. type Schema struct {\n    Metrics []MetricSchema\n    Traces  []TraceSchema\n    Logs    *LogSchema\n    Context *ContextSchema\n    Stdout  bool\n}",{"id":843,"title":203,"titles":844,"content":845,"level":40},"/v1.0.3/reference/api#metricschema",[789,198],"type MetricSchema struct {\n    Signal      string\n    Name        string\n    Type        string\n    ValueKey    string\n    Description string\n} FieldTypeRequiredDescriptionSignalstringYesSignal name to observeNamestringYesOTEL metric nameTypestringNocounter (default), gauge, histogram, updowncounterValueKeystringFor non-countersField name to extract value fromDescriptionstringNoMetric description Example: schema := aperture.Schema{\n    Metrics: []aperture.MetricSchema{\n        {Signal: \"order.created\", Name: \"orders_total\", Type: \"counter\"},\n        {Signal: \"request.done\", Name: \"request_duration_ms\", Type: \"histogram\", ValueKey: \"duration\"},\n        {Signal: \"system.cpu\", Name: \"cpu_usage\", Type: \"gauge\", ValueKey: \"percent\"},\n    },\n}",{"id":847,"title":208,"titles":848,"content":849,"level":40},"/v1.0.3/reference/api#traceschema",[789,198],"type TraceSchema struct {\n    Start          string\n    End            string\n    CorrelationKey string\n    SpanName       string\n    SpanTimeout    string\n} FieldTypeRequiredDescriptionStartstringYesSignal name that starts the spanEndstringYesSignal name that ends the spanCorrelationKeystringYesString field name to match start/endSpanNamestringNoDefaults to start signal nameSpanTimeoutstringNoDuration string (e.g., \"5m\", \"30s\"). Default: 5 minutes Example: schema := aperture.Schema{\n    Traces: []aperture.TraceSchema{\n        {\n            Start:          \"request.started\",\n            End:            \"request.completed\",\n            CorrelationKey: \"request_id\",\n            SpanName:       \"http_request\",\n            SpanTimeout:    \"1m\",\n        },\n    },\n}",{"id":851,"title":852,"titles":853,"content":854,"level":40},"/v1.0.3/reference/api#logschema","LogSchema",[789,198],"type LogSchema struct {\n    Whitelist []string\n} FieldTypeDescriptionWhitelist[]stringSignal names to log. Empty or nil = log all events Example: schema := aperture.Schema{\n    Logs: &aperture.LogSchema{\n        Whitelist: []string{\"order.created\", \"order.failed\"},\n    },\n}",{"id":856,"title":857,"titles":858,"content":859,"level":40},"/v1.0.3/reference/api#contextschema","ContextSchema",[789,198],"type ContextSchema struct {\n    Logs    []string\n    Metrics []string\n    Traces  []string\n} FieldTypeDescriptionLogs[]stringContext key names to add to log attributesMetrics[]stringContext key names to add to metric attributesTraces[]stringContext key names to add to span attributes Context keys must be registered with RegisterContextKey() before use. Example: ap.RegisterContextKey(\"user_id\", userIDKey)\nap.RegisterContextKey(\"tenant_id\", tenantIDKey)\n\nschema := aperture.Schema{\n    Context: &aperture.ContextSchema{\n        Logs:    []string{\"user_id\", \"tenant_id\"},\n        Metrics: []string{\"tenant_id\"},\n    },\n}",{"id":861,"title":862,"titles":863,"content":864,"level":40},"/v1.0.3/reference/api#stdout","Stdout",[789,198],"type Schema struct {\n    // ...\n    Stdout bool\n} When true, events are also logged to stdout in addition to OTEL.",{"id":866,"title":867,"titles":868,"content":34,"level":19},"/v1.0.3/reference/api#schema-loading","Schema Loading",[789],{"id":870,"title":871,"titles":872,"content":873,"level":40},"/v1.0.3/reference/api#loadschemafromyaml","LoadSchemaFromYAML",[789,867],"func LoadSchemaFromYAML(data []byte) (Schema, error) Parses a YAML configuration into a Schema. Example YAML: metrics:\n  - signal: order.created\n    name: orders_total\n    type: counter\n  - signal: request.done\n    name: request_duration_ms\n    type: histogram\n    value_key: duration\n\ntraces:\n  - start: request.started\n    end: request.completed\n    correlation_key: request_id\n    span_name: http_request\n    span_timeout: 1m\n\nlogs:\n  whitelist:\n    - order.created\n    - order.failed\n\ncontext:\n  logs:\n    - user_id\n  metrics:\n    - tenant_id\n\nstdout: false",{"id":875,"title":876,"titles":877,"content":878,"level":40},"/v1.0.3/reference/api#loadschemafromjson","LoadSchemaFromJSON",[789,867],"func LoadSchemaFromJSON(data []byte) (Schema, error) Parses a JSON configuration into a Schema.",{"id":880,"title":881,"titles":882,"content":883,"level":40},"/v1.0.3/reference/api#schemavalidate","Schema.Validate",[789,867],"func (s Schema) Validate() error Validates schema structure. Called automatically by Apply().",{"id":885,"title":886,"titles":887,"content":888,"level":19},"/v1.0.3/reference/api#providers","Providers",[789],"type Providers struct {\n    Log   *sdklog.LoggerProvider\n    Meter *sdkmetric.MeterProvider\n    Trace *sdktrace.TracerProvider\n} Uses SDK types from go.opentelemetry.io/otel/sdk/{log,metric,trace}.",{"id":890,"title":891,"titles":892,"content":893,"level":40},"/v1.0.3/reference/api#shutdown","Shutdown",[789,886],"func (p *Providers) Shutdown(ctx context.Context) error Shuts down all providers gracefully.",{"id":895,"title":896,"titles":897,"content":898,"level":19},"/v1.0.3/reference/api#field-type-handling","Field Type Handling",[789],"Aperture automatically converts capitan field types to OTEL attributes: Capitan TypeOTEL AttributestringStringint64Int64float64Float64boolBooltime.TimeString (RFC3339)time.DurationInt64 (milliseconds)[]stringStringSlice[]int64Int64Slice[]float64Float64Slice[]boolBoolSliceCustom typesString (JSON serialized) Custom field types are automatically JSON serialized to string attributes. html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sfm-E, html code.shiki .sfm-E{--shiki-default:var(--shiki-variable)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}",{"id":900,"title":901,"titles":902,"content":903,"level":9},"/v1.0.3/reference/testing","Testing Reference",[],"Testing utilities API reference",{"id":905,"title":901,"titles":906,"content":907,"level":9},"/v1.0.3/reference/testing#testing-reference",[],"The aperture/testing package provides utilities for testing aperture configurations.",{"id":909,"title":910,"titles":911,"content":636,"level":19},"/v1.0.3/reference/testing#import","Import",[901],{"id":913,"title":886,"titles":914,"content":34,"level":19},"/v1.0.3/reference/testing#providers",[901],{"id":916,"title":917,"titles":918,"content":919,"level":40},"/v1.0.3/reference/testing#testproviders","TestProviders",[901,886],"func TestProviders(\n    ctx context.Context,\n    serviceName string,\n    serviceVersion string,\n    otlpEndpoint string,\n) (*Providers, error) Creates OTLP providers configured for testing with insecure connections. Parameters: ctx - Context for provider creationserviceName - Service name for resource attributeserviceVersion - Service version for resource attributeotlpEndpoint - OTLP collector endpoint (e.g., \"localhost:4318\") Returns: *Providers - Log, Meter, and Trace providerserror - Validation or connection errors Example: pvs, err := apertesting.TestProviders(ctx, \"test-service\", \"v1.0.0\", \"localhost:4318\")\nif err != nil {\n    t.Skip(\"collector not available\")\n}\ndefer pvs.Shutdown(ctx)\n\nap, _ := aperture.New(cap, pvs.Log, pvs.Meter, pvs.Trace)",{"id":921,"title":886,"titles":922,"content":923,"level":40},"/v1.0.3/reference/testing#providers-1",[901,886],"type Providers struct {\n    Log   *sdklog.LoggerProvider\n    Meter *sdkmetric.MeterProvider\n    Trace *sdktrace.TracerProvider\n}",{"id":925,"title":926,"titles":927,"content":893,"level":40},"/v1.0.3/reference/testing#providersshutdown","Providers.Shutdown",[901,886],{"id":929,"title":930,"titles":931,"content":34,"level":19},"/v1.0.3/reference/testing#mock-logger","Mock Logger",[901],{"id":933,"title":934,"titles":935,"content":936,"level":40},"/v1.0.3/reference/testing#newmockloggerprovider","NewMockLoggerProvider",[901,930],"func NewMockLoggerProvider() *MockLoggerProvider Creates a mock logger provider that captures log records. Example: mockLog := apertesting.NewMockLoggerProvider()\nap, _ := aperture.New(cap, mockLog, noop.NewMeterProvider(), tracenoop.NewTracerProvider())",{"id":938,"title":939,"titles":940,"content":941,"level":40},"/v1.0.3/reference/testing#mockloggerprovider","MockLoggerProvider",[901,930],"type MockLoggerProvider struct {\n    // ...\n}",{"id":943,"title":820,"titles":944,"content":945,"level":812},"/v1.0.3/reference/testing#logger",[901,930,939],"func (p *MockLoggerProvider) Logger(name string, opts ...log.LoggerOption) log.Logger Returns the mock logger (same instance for all names).",{"id":947,"title":948,"titles":949,"content":950,"level":812},"/v1.0.3/reference/testing#capture","Capture",[901,930,939],"func (p *MockLoggerProvider) Capture() *LogCapture Returns the log capture for assertions.",{"id":952,"title":953,"titles":954,"content":955,"level":40},"/v1.0.3/reference/testing#newmocklogger","NewMockLogger",[901,930],"func NewMockLogger() *MockLogger Creates a standalone mock logger.",{"id":957,"title":958,"titles":959,"content":960,"level":40},"/v1.0.3/reference/testing#mocklogger","MockLogger",[901,930],"type MockLogger struct {\n    // ...\n}",{"id":962,"title":963,"titles":964,"content":965,"level":812},"/v1.0.3/reference/testing#emit","Emit",[901,930,958],"func (m *MockLogger) Emit(ctx context.Context, record log.Record) Captures the log record.",{"id":967,"title":968,"titles":969,"content":970,"level":812},"/v1.0.3/reference/testing#enabled","Enabled",[901,930,958],"func (m *MockLogger) Enabled(ctx context.Context, params log.EnabledParameters) bool Always returns true.",{"id":972,"title":948,"titles":973,"content":974,"level":812},"/v1.0.3/reference/testing#capture-1",[901,930,958],"func (m *MockLogger) Capture() *LogCapture Returns the log capture.",{"id":976,"title":644,"titles":977,"content":34,"level":19},"/v1.0.3/reference/testing#log-capture",[901],{"id":979,"title":980,"titles":981,"content":982,"level":40},"/v1.0.3/reference/testing#newlogcapture","NewLogCapture",[901,644],"func NewLogCapture() *LogCapture Creates a new log capture instance.",{"id":984,"title":985,"titles":986,"content":987,"level":40},"/v1.0.3/reference/testing#logcapture","LogCapture",[901,644],"type LogCapture struct {\n    // ...\n} Thread-safe log record storage.",{"id":989,"title":990,"titles":991,"content":992,"level":812},"/v1.0.3/reference/testing#records","Records",[901,644,985],"func (lc *LogCapture) Records() []log.Record Returns a copy of all captured records.",{"id":994,"title":995,"titles":996,"content":997,"level":812},"/v1.0.3/reference/testing#count","Count",[901,644,985],"func (lc *LogCapture) Count() int Returns the number of captured records.",{"id":999,"title":1000,"titles":1001,"content":1002,"level":812},"/v1.0.3/reference/testing#reset","Reset",[901,644,985],"func (lc *LogCapture) Reset() Clears all captured records.",{"id":1004,"title":1005,"titles":1006,"content":1007,"level":812},"/v1.0.3/reference/testing#waitforcount","WaitForCount",[901,644,985],"func (lc *LogCapture) WaitForCount(n int, timeout time.Duration) bool Blocks until at least n records are captured or timeout expires. Returns: true if count reached, false if timeout. Example: if !capture.WaitForCount(5, time.Second) {\n    t.Error(\"timeout waiting for logs\")\n}",{"id":1009,"title":654,"titles":1010,"content":34,"level":19},"/v1.0.3/reference/testing#event-capture",[901],{"id":1012,"title":1013,"titles":1014,"content":1015,"level":40},"/v1.0.3/reference/testing#neweventcapture","NewEventCapture",[901,654],"func NewEventCapture() *EventCapture Creates a new event capture instance.",{"id":1017,"title":1018,"titles":1019,"content":1020,"level":40},"/v1.0.3/reference/testing#eventcapture","EventCapture",[901,654],"type EventCapture struct {\n    // ...\n} Thread-safe capitan event storage.",{"id":1022,"title":1023,"titles":1024,"content":1025,"level":812},"/v1.0.3/reference/testing#handler","Handler",[901,654,1018],"func (ec *EventCapture) Handler() capitan.EventCallback Returns a callback for capitan.Hook(). Example: capture := apertesting.NewEventCapture()\ncap.Hook(sig, capture.Handler())",{"id":1027,"title":1028,"titles":1029,"content":1030,"level":812},"/v1.0.3/reference/testing#events","Events",[901,654,1018],"func (ec *EventCapture) Events() []CapturedEvent Returns a copy of all captured events.",{"id":1032,"title":995,"titles":1033,"content":1034,"level":812},"/v1.0.3/reference/testing#count-1",[901,654,1018],"func (ec *EventCapture) Count() int Returns the number of captured events.",{"id":1036,"title":1000,"titles":1037,"content":1038,"level":812},"/v1.0.3/reference/testing#reset-1",[901,654,1018],"func (ec *EventCapture) Reset() Clears all captured events.",{"id":1040,"title":1005,"titles":1041,"content":1042,"level":812},"/v1.0.3/reference/testing#waitforcount-1",[901,654,1018],"func (ec *EventCapture) WaitForCount(n int, timeout time.Duration) bool Blocks until at least n events are captured or timeout expires.",{"id":1044,"title":1045,"titles":1046,"content":1047,"level":40},"/v1.0.3/reference/testing#capturedevent","CapturedEvent",[901,654],"type CapturedEvent struct {\n    Signal    capitan.Signal\n    Timestamp time.Time\n    Severity  capitan.Severity\n    Fields    []capitan.Field\n} Snapshot of a capitan event.",{"id":1049,"title":1050,"titles":1051,"content":1052,"level":19},"/v1.0.3/reference/testing#usage-with-noop-providers","Usage with Noop Providers",[901],"For tests that only verify logging: import (\n    \"go.opentelemetry.io/otel/metric/noop\"\n    tracenoop \"go.opentelemetry.io/otel/trace/noop\"\n)\n\nfunc TestLogging(t *testing.T) {\n    mockLog := apertesting.NewMockLoggerProvider()\n\n    ap, _ := aperture.New(\n        cap,\n        mockLog,\n        noop.NewMeterProvider(),\n        tracenoop.NewTracerProvider(),\n    )\n    // ...\n}",{"id":1054,"title":1055,"titles":1056,"content":34,"level":19},"/v1.0.3/reference/testing#common-test-patterns","Common Test Patterns",[901],{"id":1058,"title":1059,"titles":1060,"content":1061,"level":40},"/v1.0.3/reference/testing#verify-log-count","Verify Log Count",[901,1055],"func TestLogCount(t *testing.T) {\n    mockLog := apertesting.NewMockLoggerProvider()\n    ap, _ := aperture.New(cap, mockLog, noop.NewMeterProvider(), tracenoop.NewTracerProvider())\n    defer ap.Close()\n\n    cap.Emit(ctx, sig)\n    cap.Emit(ctx, sig)\n    cap.Shutdown()\n\n    if mockLog.Capture().Count() != 2 {\n        t.Errorf(\"expected 2 logs\")\n    }\n}",{"id":1063,"title":1064,"titles":1065,"content":1066,"level":40},"/v1.0.3/reference/testing#verify-whitelist-filtering","Verify Whitelist Filtering",[901,1055],"func TestWhitelist(t *testing.T) {\n    allowed := capitan.NewSignal(\"allowed\", \"\")\n    blocked := capitan.NewSignal(\"blocked\", \"\")\n\n    schema := aperture.Schema{\n        Logs: &aperture.LogSchema{\n            Whitelist: []string{\"allowed\"},\n        },\n    }\n\n    mockLog := apertesting.NewMockLoggerProvider()\n    ap, _ := aperture.New(cap, mockLog, noop.NewMeterProvider(), tracenoop.NewTracerProvider())\n    ap.Apply(schema)\n    defer ap.Close()\n\n    cap.Emit(ctx, allowed)\n    cap.Emit(ctx, blocked)\n    cap.Shutdown()\n\n    if mockLog.Capture().Count() != 1 {\n        t.Error(\"whitelist filtering failed\")\n    }\n}",{"id":1068,"title":1069,"titles":1070,"content":1071,"level":40},"/v1.0.3/reference/testing#wait-for-async-events","Wait for Async Events",[901,1055],"func TestAsync(t *testing.T) {\n    mockLog := apertesting.NewMockLoggerProvider()\n    // ... setup ...\n\n    go func() {\n        for i := 0; i \u003C 10; i++ {\n            cap.Emit(ctx, sig)\n        }\n    }()\n\n    if !mockLog.Capture().WaitForCount(10, 5*time.Second) {\n        t.Error(\"timeout\")\n    }\n} html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}",[1073],{"title":1074,"path":1075,"stem":1076,"children":1077,"page":1091},"V103","/v1.0.3","v1.0.3",[1078,1080,1092,1109,1118],{"title":6,"path":5,"stem":1079,"description":8},"v1.0.3/1.overview",{"title":1081,"path":1082,"stem":1083,"children":1084,"page":1091},"Learn","/v1.0.3/learn","v1.0.3/2.learn",[1085,1087,1089],{"title":53,"path":52,"stem":1086,"description":55},"v1.0.3/2.learn/1.quickstart",{"title":91,"path":90,"stem":1088,"description":93},"v1.0.3/2.learn/2.concepts",{"title":16,"path":171,"stem":1090,"description":173},"v1.0.3/2.learn/3.architecture",false,{"title":1093,"path":1094,"stem":1095,"children":1096,"page":1091},"Guides","/v1.0.3/guides","v1.0.3/3.guides",[1097,1099,1101,1103,1105,1107],{"title":138,"path":274,"stem":1098,"description":276},"v1.0.3/3.guides/1.metrics",{"title":143,"path":337,"stem":1100,"description":339},"v1.0.3/3.guides/2.traces",{"title":133,"path":414,"stem":1102,"description":416},"v1.0.3/3.guides/3.logs",{"title":153,"path":481,"stem":1104,"description":483},"v1.0.3/3.guides/4.context",{"title":333,"path":533,"stem":1106,"description":535},"v1.0.3/3.guides/5.schema",{"title":625,"path":624,"stem":1108,"description":627},"v1.0.3/3.guides/6.testing",{"title":1110,"path":1111,"stem":1112,"children":1113,"page":1091},"Cookbook","/v1.0.3/cookbook","v1.0.3/4.cookbook",[1114,1116],{"title":689,"path":688,"stem":1115,"description":691},"v1.0.3/4.cookbook/1.http-server",{"title":738,"path":737,"stem":1117,"description":740},"v1.0.3/4.cookbook/2.background-workers",{"title":1119,"path":1120,"stem":1121,"children":1122,"page":1091},"Reference","/v1.0.3/reference","v1.0.3/5.reference",[1123,1125],{"title":789,"path":788,"stem":1124,"description":791},"v1.0.3/5.reference/1.api",{"title":901,"path":900,"stem":1126,"description":903},"v1.0.3/5.reference/2.testing",[1128],{"title":1074,"path":1075,"stem":1076,"children":1129,"page":1091},[1130,1131,1136,1144,1148],{"title":6,"path":5,"stem":1079},{"title":1081,"path":1082,"stem":1083,"children":1132,"page":1091},[1133,1134,1135],{"title":53,"path":52,"stem":1086},{"title":91,"path":90,"stem":1088},{"title":16,"path":171,"stem":1090},{"title":1093,"path":1094,"stem":1095,"children":1137,"page":1091},[1138,1139,1140,1141,1142,1143],{"title":138,"path":274,"stem":1098},{"title":143,"path":337,"stem":1100},{"title":133,"path":414,"stem":1102},{"title":153,"path":481,"stem":1104},{"title":333,"path":533,"stem":1106},{"title":625,"path":624,"stem":1108},{"title":1110,"path":1111,"stem":1112,"children":1145,"page":1091},[1146,1147],{"title":689,"path":688,"stem":1115},{"title":738,"path":737,"stem":1117},{"title":1119,"path":1120,"stem":1121,"children":1149,"page":1091},[1150,1151],{"title":789,"path":788,"stem":1124},{"title":901,"path":900,"stem":1126},[1153,2827,3251],{"id":1154,"title":1155,"body":1156,"description":34,"extension":2820,"icon":2821,"meta":2822,"navigation":1335,"path":2823,"seo":2824,"stem":2825,"__hash__":2826},"resources/readme.md","README",{"type":1157,"value":1158,"toc":2804},"minimark",[1159,1163,1231,1240,1243,1248,1383,1532,1535,1538,1555,1558,1562,2242,2248,2395,2399,2451,2455,2461,2464,2576,2579,2582,2679,2683,2690,2694,2714,2717,2749,2752,2768,2771,2779,2783,2791,2794,2800],[1160,1161,1162],"h1",{"id":1162},"aperture",[1164,1165,1166,1177,1185,1193,1201,1209,1216,1223],"p",{},[1167,1168,1172],"a",{"href":1169,"rel":1170},"https://github.com/zoobz-io/aperture/actions/workflows/ci.yml",[1171],"nofollow",[1173,1174],"img",{"alt":1175,"src":1176},"CI Status","https://github.com/zoobz-io/aperture/workflows/CI/badge.svg",[1167,1178,1181],{"href":1179,"rel":1180},"https://codecov.io/gh/zoobz-io/aperture",[1171],[1173,1182],{"alt":1183,"src":1184},"codecov","https://codecov.io/gh/zoobz-io/aperture/graph/badge.svg?branch=main",[1167,1186,1189],{"href":1187,"rel":1188},"https://goreportcard.com/report/github.com/zoobz-io/aperture",[1171],[1173,1190],{"alt":1191,"src":1192},"Go Report Card","https://goreportcard.com/badge/github.com/zoobz-io/aperture",[1167,1194,1197],{"href":1195,"rel":1196},"https://github.com/zoobz-io/aperture/security/code-scanning",[1171],[1173,1198],{"alt":1199,"src":1200},"CodeQL","https://github.com/zoobz-io/aperture/workflows/CodeQL/badge.svg",[1167,1202,1205],{"href":1203,"rel":1204},"https://pkg.go.dev/github.com/zoobz-io/aperture",[1171],[1173,1206],{"alt":1207,"src":1208},"Go Reference","https://pkg.go.dev/badge/github.com/zoobz-io/aperture.svg",[1167,1210,1212],{"href":1211},"LICENSE",[1173,1213],{"alt":1214,"src":1215},"License","https://img.shields.io/github/license/zoobz-io/aperture",[1167,1217,1219],{"href":1218},"go.mod",[1173,1220],{"alt":1221,"src":1222},"Go Version","https://img.shields.io/github/go-mod/go-version/zoobz-io/aperture",[1167,1224,1227],{"href":1225,"rel":1226},"https://github.com/zoobz-io/aperture/releases",[1171],[1173,1228],{"alt":1229,"src":1230},"Release","https://img.shields.io/github/v/release/zoobz-io/aperture",[1164,1232,1233,1234,1239],{},"Config-driven bridge from ",[1167,1235,1238],{"href":1236,"rel":1237},"https://github.com/zoobz-io/capitan",[1171],"capitan"," events to OpenTelemetry signals.",[1164,1241,1242],{},"Emit a capitan event, aperture transforms it into OTEL logs, metrics, and traces. Define your signals in code, configure observability in YAML, change what's observed at runtime without recompiling.",[1244,1245,1247],"h2",{"id":1246},"config-driven-bridge","Config-Driven Bridge",[1249,1250,1254],"pre",{"className":1251,"code":1252,"language":1253,"meta":34,"style":34},"language-go shiki shiki-themes","// Create aperture with your OTEL providers\nap, _ := aperture.New(cap, logProvider, meterProvider, traceProvider)\ndefer ap.Close()\n\n// Load and apply configuration\nschema, _ := aperture.LoadSchemaFromYAML(configBytes)\nap.Apply(schema)\n","go",[1255,1256,1257,1265,1315,1331,1337,1343,1368],"code",{"__ignoreMap":34},[1258,1259,1261],"span",{"class":1260,"line":9},"line",[1258,1262,1264],{"class":1263},"sLkEo","// Create aperture with your OTEL providers\n",[1258,1266,1267,1271,1275,1278,1281,1284,1287,1291,1294,1297,1299,1302,1304,1307,1309,1312],{"class":1260,"line":19},[1258,1268,1270],{"class":1269},"sh8_p","ap",[1258,1272,1274],{"class":1273},"sq5bi",",",[1258,1276,1277],{"class":1269}," _",[1258,1279,1280],{"class":1269}," :=",[1258,1282,1283],{"class":1269}," aperture",[1258,1285,1286],{"class":1273},".",[1258,1288,1290],{"class":1289},"s5klm","New",[1258,1292,1293],{"class":1273},"(",[1258,1295,1296],{"class":1269},"cap",[1258,1298,1274],{"class":1273},[1258,1300,1301],{"class":1269}," logProvider",[1258,1303,1274],{"class":1273},[1258,1305,1306],{"class":1269}," meterProvider",[1258,1308,1274],{"class":1273},[1258,1310,1311],{"class":1269}," traceProvider",[1258,1313,1314],{"class":1273},")\n",[1258,1316,1317,1321,1324,1326,1328],{"class":1260,"line":40},[1258,1318,1320],{"class":1319},"sW3Qg","defer",[1258,1322,1323],{"class":1269}," ap",[1258,1325,1286],{"class":1273},[1258,1327,835],{"class":1289},[1258,1329,1330],{"class":1273},"()\n",[1258,1332,1333],{"class":1260,"line":812},[1258,1334,1336],{"emptyLinePlaceholder":1335},true,"\n",[1258,1338,1340],{"class":1260,"line":1339},5,[1258,1341,1342],{"class":1263},"// Load and apply configuration\n",[1258,1344,1346,1349,1351,1353,1355,1357,1359,1361,1363,1366],{"class":1260,"line":1345},6,[1258,1347,1348],{"class":1269},"schema",[1258,1350,1274],{"class":1273},[1258,1352,1277],{"class":1269},[1258,1354,1280],{"class":1269},[1258,1356,1283],{"class":1269},[1258,1358,1286],{"class":1273},[1258,1360,871],{"class":1289},[1258,1362,1293],{"class":1273},[1258,1364,1365],{"class":1269},"configBytes",[1258,1367,1314],{"class":1273},[1258,1369,1371,1373,1375,1377,1379,1381],{"class":1260,"line":1370},7,[1258,1372,1270],{"class":1269},[1258,1374,1286],{"class":1273},[1258,1376,809],{"class":1289},[1258,1378,1293],{"class":1273},[1258,1380,1348],{"class":1269},[1258,1382,1314],{"class":1273},[1249,1384,1388],{"className":1385,"code":1386,"language":1387,"meta":34,"style":34},"language-yaml shiki shiki-themes","# observability.yaml\nmetrics:\n  - signal: order.created\n    name: orders_total\n    type: counter\n\ntraces:\n  - start: order.created\n    end: order.completed\n    correlation_key: order_id\n    span_name: order_processing\n\nlogs:\n  whitelist:\n    - order.created\n    - order.completed\n","yaml",[1255,1389,1390,1395,1405,1420,1430,1440,1444,1451,1463,1474,1485,1496,1501,1509,1517,1525],{"__ignoreMap":34},[1258,1391,1392],{"class":1260,"line":9},[1258,1393,1394],{"class":1263},"# observability.yaml\n",[1258,1396,1397,1401],{"class":1260,"line":19},[1258,1398,1400],{"class":1399},"sUt3r","metrics",[1258,1402,1404],{"class":1403},"soy-K",":\n",[1258,1406,1407,1410,1413,1416],{"class":1260,"line":40},[1258,1408,1409],{"class":1403},"  - ",[1258,1411,1412],{"class":1399},"signal",[1258,1414,1415],{"class":1403},": ",[1258,1417,1419],{"class":1418},"sxAnc","order.created\n",[1258,1421,1422,1425,1427],{"class":1260,"line":812},[1258,1423,1424],{"class":1399},"    name",[1258,1426,1415],{"class":1403},[1258,1428,1429],{"class":1418},"orders_total\n",[1258,1431,1432,1435,1437],{"class":1260,"line":1339},[1258,1433,1434],{"class":1399},"    type",[1258,1436,1415],{"class":1403},[1258,1438,1439],{"class":1418},"counter\n",[1258,1441,1442],{"class":1260,"line":1345},[1258,1443,1336],{"emptyLinePlaceholder":1335},[1258,1445,1446,1449],{"class":1260,"line":1370},[1258,1447,1448],{"class":1399},"traces",[1258,1450,1404],{"class":1403},[1258,1452,1454,1456,1459,1461],{"class":1260,"line":1453},8,[1258,1455,1409],{"class":1403},[1258,1457,1458],{"class":1399},"start",[1258,1460,1415],{"class":1403},[1258,1462,1419],{"class":1418},[1258,1464,1466,1469,1471],{"class":1260,"line":1465},9,[1258,1467,1468],{"class":1399},"    end",[1258,1470,1415],{"class":1403},[1258,1472,1473],{"class":1418},"order.completed\n",[1258,1475,1477,1480,1482],{"class":1260,"line":1476},10,[1258,1478,1479],{"class":1399},"    correlation_key",[1258,1481,1415],{"class":1403},[1258,1483,1484],{"class":1418},"order_id\n",[1258,1486,1488,1491,1493],{"class":1260,"line":1487},11,[1258,1489,1490],{"class":1399},"    span_name",[1258,1492,1415],{"class":1403},[1258,1494,1495],{"class":1418},"order_processing\n",[1258,1497,1499],{"class":1260,"line":1498},12,[1258,1500,1336],{"emptyLinePlaceholder":1335},[1258,1502,1504,1507],{"class":1260,"line":1503},13,[1258,1505,1506],{"class":1399},"logs",[1258,1508,1404],{"class":1403},[1258,1510,1512,1515],{"class":1260,"line":1511},14,[1258,1513,1514],{"class":1399},"  whitelist",[1258,1516,1404],{"class":1403},[1258,1518,1520,1523],{"class":1260,"line":1519},15,[1258,1521,1522],{"class":1403},"    - ",[1258,1524,1419],{"class":1418},[1258,1526,1528,1530],{"class":1260,"line":1527},16,[1258,1529,1522],{"class":1403},[1258,1531,1473],{"class":1418},[1164,1533,1534],{},"Signals are type-safe. Configuration is data. Change what's observed without touching code.",[1244,1536,61],{"id":1537},"installation",[1249,1539,1543],{"className":1540,"code":1541,"language":1542,"meta":34,"style":34},"language-bash shiki shiki-themes","go get github.com/zoobz-io/aperture\n","bash",[1255,1544,1545],{"__ignoreMap":34},[1258,1546,1547,1549,1552],{"class":1260,"line":9},[1258,1548,1253],{"class":1289},[1258,1550,1551],{"class":1418}," get",[1258,1553,1554],{"class":1418}," github.com/zoobz-io/aperture\n",[1164,1556,1557],{},"Requires Go 1.24+.",[1244,1559,1561],{"id":1560},"quick-start","Quick Start",[1249,1563,1565],{"className":1251,"code":1564,"language":1253,"meta":34,"style":34},"package main\n\nimport (\n    \"context\"\n    \"log\"\n    \"os\"\n    \"time\"\n\n    \"github.com/zoobz-io/aperture\"\n    \"github.com/zoobz-io/capitan\"\n    // OTEL imports omitted for brevity\n)\n\n// Domain signals - defined once, used everywhere\nvar (\n    orderCreated   = capitan.NewSignal(\"order.created\", \"Order placed\")\n    orderCompleted = capitan.NewSignal(\"order.completed\", \"Order fulfilled\")\n    orderID        = capitan.NewStringKey(\"order_id\")\n    duration       = capitan.NewDurationKey(\"duration\")\n)\n\nfunc main() {\n    ctx := context.Background()\n\n    // Create OTEL providers (your setup, your exporters)\n    logProvider, meterProvider, traceProvider := setupOTEL()\n\n    // Create aperture bridge\n    ap, err := aperture.New(capitan.Default(), logProvider, meterProvider, traceProvider)\n    if err != nil {\n        log.Fatal(err)\n    }\n    defer ap.Close()\n\n    // Load and apply configuration\n    configBytes, err := os.ReadFile(\"observability.yaml\")\n    if err != nil {\n        log.Fatal(err)\n    }\n    schema, err := aperture.LoadSchemaFromYAML(configBytes)\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := ap.Apply(schema); err != nil {\n        log.Fatal(err)\n    }\n\n    // Emit domain events - aperture handles observability\n    capitan.Emit(ctx, orderCreated, orderID.Field(\"ORD-123\"))\n    capitan.Emit(ctx, orderCompleted, orderID.Field(\"ORD-123\"), duration.Field(250*time.Millisecond))\n    // → Logged, counted, traced - based on config\n\n    capitan.Shutdown()\n}\n",[1255,1566,1567,1576,1580,1588,1593,1598,1603,1608,1612,1617,1622,1627,1631,1635,1640,1647,1675,1702,1725,1748,1753,1758,1773,1791,1796,1802,1823,1828,1834,1877,1893,1911,1917,1931,1936,1942,1969,1982,1997,2002,2026,2039,2054,2059,2089,2104,2109,2114,2120,2158,2214,2220,2225,2236],{"__ignoreMap":34},[1258,1568,1569,1572],{"class":1260,"line":9},[1258,1570,1571],{"class":1399},"package",[1258,1573,1575],{"class":1574},"sYBwO"," main\n",[1258,1577,1578],{"class":1260,"line":19},[1258,1579,1336],{"emptyLinePlaceholder":1335},[1258,1581,1582,1585],{"class":1260,"line":40},[1258,1583,1584],{"class":1399},"import",[1258,1586,1587],{"class":1403}," (\n",[1258,1589,1590],{"class":1260,"line":812},[1258,1591,1592],{"class":1418},"    \"context\"\n",[1258,1594,1595],{"class":1260,"line":1339},[1258,1596,1597],{"class":1418},"    \"log\"\n",[1258,1599,1600],{"class":1260,"line":1345},[1258,1601,1602],{"class":1418},"    \"os\"\n",[1258,1604,1605],{"class":1260,"line":1370},[1258,1606,1607],{"class":1418},"    \"time\"\n",[1258,1609,1610],{"class":1260,"line":1453},[1258,1611,1336],{"emptyLinePlaceholder":1335},[1258,1613,1614],{"class":1260,"line":1465},[1258,1615,1616],{"class":1418},"    \"github.com/zoobz-io/aperture\"\n",[1258,1618,1619],{"class":1260,"line":1476},[1258,1620,1621],{"class":1418},"    \"github.com/zoobz-io/capitan\"\n",[1258,1623,1624],{"class":1260,"line":1487},[1258,1625,1626],{"class":1263},"    // OTEL imports omitted for brevity\n",[1258,1628,1629],{"class":1260,"line":1498},[1258,1630,1314],{"class":1403},[1258,1632,1633],{"class":1260,"line":1503},[1258,1634,1336],{"emptyLinePlaceholder":1335},[1258,1636,1637],{"class":1260,"line":1511},[1258,1638,1639],{"class":1263},"// Domain signals - defined once, used everywhere\n",[1258,1641,1642,1645],{"class":1260,"line":1519},[1258,1643,1644],{"class":1399},"var",[1258,1646,1587],{"class":1273},[1258,1648,1649,1652,1655,1658,1660,1663,1665,1668,1670,1673],{"class":1260,"line":1527},[1258,1650,1651],{"class":1269},"    orderCreated",[1258,1653,1654],{"class":1269},"   =",[1258,1656,1657],{"class":1269}," capitan",[1258,1659,1286],{"class":1273},[1258,1661,1662],{"class":1289},"NewSignal",[1258,1664,1293],{"class":1273},[1258,1666,1667],{"class":1418},"\"order.created\"",[1258,1669,1274],{"class":1273},[1258,1671,1672],{"class":1418}," \"Order placed\"",[1258,1674,1314],{"class":1273},[1258,1676,1678,1681,1684,1686,1688,1690,1692,1695,1697,1700],{"class":1260,"line":1677},17,[1258,1679,1680],{"class":1269},"    orderCompleted",[1258,1682,1683],{"class":1269}," =",[1258,1685,1657],{"class":1269},[1258,1687,1286],{"class":1273},[1258,1689,1662],{"class":1289},[1258,1691,1293],{"class":1273},[1258,1693,1694],{"class":1418},"\"order.completed\"",[1258,1696,1274],{"class":1273},[1258,1698,1699],{"class":1418}," \"Order fulfilled\"",[1258,1701,1314],{"class":1273},[1258,1703,1705,1708,1711,1713,1715,1718,1720,1723],{"class":1260,"line":1704},18,[1258,1706,1707],{"class":1269},"    orderID",[1258,1709,1710],{"class":1269},"        =",[1258,1712,1657],{"class":1269},[1258,1714,1286],{"class":1273},[1258,1716,1717],{"class":1289},"NewStringKey",[1258,1719,1293],{"class":1273},[1258,1721,1722],{"class":1418},"\"order_id\"",[1258,1724,1314],{"class":1273},[1258,1726,1728,1731,1734,1736,1738,1741,1743,1746],{"class":1260,"line":1727},19,[1258,1729,1730],{"class":1269},"    duration",[1258,1732,1733],{"class":1269},"       =",[1258,1735,1657],{"class":1269},[1258,1737,1286],{"class":1273},[1258,1739,1740],{"class":1289},"NewDurationKey",[1258,1742,1293],{"class":1273},[1258,1744,1745],{"class":1418},"\"duration\"",[1258,1747,1314],{"class":1273},[1258,1749,1751],{"class":1260,"line":1750},20,[1258,1752,1314],{"class":1273},[1258,1754,1756],{"class":1260,"line":1755},21,[1258,1757,1336],{"emptyLinePlaceholder":1335},[1258,1759,1761,1764,1767,1770],{"class":1260,"line":1760},22,[1258,1762,1763],{"class":1399},"func",[1258,1765,1766],{"class":1289}," main",[1258,1768,1769],{"class":1273},"()",[1258,1771,1772],{"class":1273}," {\n",[1258,1774,1776,1779,1781,1784,1786,1789],{"class":1260,"line":1775},23,[1258,1777,1778],{"class":1269},"    ctx",[1258,1780,1280],{"class":1269},[1258,1782,1783],{"class":1269}," context",[1258,1785,1286],{"class":1273},[1258,1787,1788],{"class":1289},"Background",[1258,1790,1330],{"class":1273},[1258,1792,1794],{"class":1260,"line":1793},24,[1258,1795,1336],{"emptyLinePlaceholder":1335},[1258,1797,1799],{"class":1260,"line":1798},25,[1258,1800,1801],{"class":1263},"    // Create OTEL providers (your setup, your exporters)\n",[1258,1803,1805,1808,1810,1812,1814,1816,1818,1821],{"class":1260,"line":1804},26,[1258,1806,1807],{"class":1269},"    logProvider",[1258,1809,1274],{"class":1273},[1258,1811,1306],{"class":1269},[1258,1813,1274],{"class":1273},[1258,1815,1311],{"class":1269},[1258,1817,1280],{"class":1269},[1258,1819,1820],{"class":1289}," setupOTEL",[1258,1822,1330],{"class":1273},[1258,1824,1826],{"class":1260,"line":1825},27,[1258,1827,1336],{"emptyLinePlaceholder":1335},[1258,1829,1831],{"class":1260,"line":1830},28,[1258,1832,1833],{"class":1263},"    // Create aperture bridge\n",[1258,1835,1837,1840,1842,1845,1847,1849,1851,1853,1855,1857,1859,1862,1865,1867,1869,1871,1873,1875],{"class":1260,"line":1836},29,[1258,1838,1839],{"class":1269},"    ap",[1258,1841,1274],{"class":1273},[1258,1843,1844],{"class":1269}," err",[1258,1846,1280],{"class":1269},[1258,1848,1283],{"class":1269},[1258,1850,1286],{"class":1273},[1258,1852,1290],{"class":1289},[1258,1854,1293],{"class":1273},[1258,1856,1238],{"class":1269},[1258,1858,1286],{"class":1273},[1258,1860,1861],{"class":1289},"Default",[1258,1863,1864],{"class":1273},"(),",[1258,1866,1301],{"class":1269},[1258,1868,1274],{"class":1273},[1258,1870,1306],{"class":1269},[1258,1872,1274],{"class":1273},[1258,1874,1311],{"class":1269},[1258,1876,1314],{"class":1273},[1258,1878,1880,1883,1885,1888,1891],{"class":1260,"line":1879},30,[1258,1881,1882],{"class":1319},"    if",[1258,1884,1844],{"class":1269},[1258,1886,1887],{"class":1319}," !=",[1258,1889,1890],{"class":1399}," nil",[1258,1892,1772],{"class":1273},[1258,1894,1896,1899,1901,1904,1906,1909],{"class":1260,"line":1895},31,[1258,1897,1898],{"class":1269},"        log",[1258,1900,1286],{"class":1273},[1258,1902,1903],{"class":1289},"Fatal",[1258,1905,1293],{"class":1273},[1258,1907,1908],{"class":1269},"err",[1258,1910,1314],{"class":1273},[1258,1912,1914],{"class":1260,"line":1913},32,[1258,1915,1916],{"class":1273},"    }\n",[1258,1918,1920,1923,1925,1927,1929],{"class":1260,"line":1919},33,[1258,1921,1922],{"class":1319},"    defer",[1258,1924,1323],{"class":1269},[1258,1926,1286],{"class":1273},[1258,1928,835],{"class":1289},[1258,1930,1330],{"class":1273},[1258,1932,1934],{"class":1260,"line":1933},34,[1258,1935,1336],{"emptyLinePlaceholder":1335},[1258,1937,1939],{"class":1260,"line":1938},35,[1258,1940,1941],{"class":1263},"    // Load and apply configuration\n",[1258,1943,1945,1948,1950,1952,1954,1957,1959,1962,1964,1967],{"class":1260,"line":1944},36,[1258,1946,1947],{"class":1269},"    configBytes",[1258,1949,1274],{"class":1273},[1258,1951,1844],{"class":1269},[1258,1953,1280],{"class":1269},[1258,1955,1956],{"class":1269}," os",[1258,1958,1286],{"class":1273},[1258,1960,1961],{"class":1289},"ReadFile",[1258,1963,1293],{"class":1273},[1258,1965,1966],{"class":1418},"\"observability.yaml\"",[1258,1968,1314],{"class":1273},[1258,1970,1972,1974,1976,1978,1980],{"class":1260,"line":1971},37,[1258,1973,1882],{"class":1319},[1258,1975,1844],{"class":1269},[1258,1977,1887],{"class":1319},[1258,1979,1890],{"class":1399},[1258,1981,1772],{"class":1273},[1258,1983,1985,1987,1989,1991,1993,1995],{"class":1260,"line":1984},38,[1258,1986,1898],{"class":1269},[1258,1988,1286],{"class":1273},[1258,1990,1903],{"class":1289},[1258,1992,1293],{"class":1273},[1258,1994,1908],{"class":1269},[1258,1996,1314],{"class":1273},[1258,1998,2000],{"class":1260,"line":1999},39,[1258,2001,1916],{"class":1273},[1258,2003,2005,2008,2010,2012,2014,2016,2018,2020,2022,2024],{"class":1260,"line":2004},40,[1258,2006,2007],{"class":1269},"    schema",[1258,2009,1274],{"class":1273},[1258,2011,1844],{"class":1269},[1258,2013,1280],{"class":1269},[1258,2015,1283],{"class":1269},[1258,2017,1286],{"class":1273},[1258,2019,871],{"class":1289},[1258,2021,1293],{"class":1273},[1258,2023,1365],{"class":1269},[1258,2025,1314],{"class":1273},[1258,2027,2029,2031,2033,2035,2037],{"class":1260,"line":2028},41,[1258,2030,1882],{"class":1319},[1258,2032,1844],{"class":1269},[1258,2034,1887],{"class":1319},[1258,2036,1890],{"class":1399},[1258,2038,1772],{"class":1273},[1258,2040,2042,2044,2046,2048,2050,2052],{"class":1260,"line":2041},42,[1258,2043,1898],{"class":1269},[1258,2045,1286],{"class":1273},[1258,2047,1903],{"class":1289},[1258,2049,1293],{"class":1273},[1258,2051,1908],{"class":1269},[1258,2053,1314],{"class":1273},[1258,2055,2057],{"class":1260,"line":2056},43,[1258,2058,1916],{"class":1273},[1258,2060,2062,2064,2066,2068,2070,2072,2074,2076,2078,2081,2083,2085,2087],{"class":1260,"line":2061},44,[1258,2063,1882],{"class":1319},[1258,2065,1844],{"class":1269},[1258,2067,1280],{"class":1269},[1258,2069,1323],{"class":1269},[1258,2071,1286],{"class":1273},[1258,2073,809],{"class":1289},[1258,2075,1293],{"class":1273},[1258,2077,1348],{"class":1269},[1258,2079,2080],{"class":1273},");",[1258,2082,1844],{"class":1269},[1258,2084,1887],{"class":1319},[1258,2086,1890],{"class":1399},[1258,2088,1772],{"class":1273},[1258,2090,2092,2094,2096,2098,2100,2102],{"class":1260,"line":2091},45,[1258,2093,1898],{"class":1269},[1258,2095,1286],{"class":1273},[1258,2097,1903],{"class":1289},[1258,2099,1293],{"class":1273},[1258,2101,1908],{"class":1269},[1258,2103,1314],{"class":1273},[1258,2105,2107],{"class":1260,"line":2106},46,[1258,2108,1916],{"class":1273},[1258,2110,2112],{"class":1260,"line":2111},47,[1258,2113,1336],{"emptyLinePlaceholder":1335},[1258,2115,2117],{"class":1260,"line":2116},48,[1258,2118,2119],{"class":1263},"    // Emit domain events - aperture handles observability\n",[1258,2121,2123,2126,2128,2130,2132,2135,2137,2140,2142,2145,2147,2150,2152,2155],{"class":1260,"line":2122},49,[1258,2124,2125],{"class":1269},"    capitan",[1258,2127,1286],{"class":1273},[1258,2129,963],{"class":1289},[1258,2131,1293],{"class":1273},[1258,2133,2134],{"class":1269},"ctx",[1258,2136,1274],{"class":1273},[1258,2138,2139],{"class":1269}," orderCreated",[1258,2141,1274],{"class":1273},[1258,2143,2144],{"class":1269}," orderID",[1258,2146,1286],{"class":1273},[1258,2148,2149],{"class":1289},"Field",[1258,2151,1293],{"class":1273},[1258,2153,2154],{"class":1418},"\"ORD-123\"",[1258,2156,2157],{"class":1273},"))\n",[1258,2159,2161,2163,2165,2167,2169,2171,2173,2176,2178,2180,2182,2184,2186,2188,2191,2194,2196,2198,2200,2204,2207,2209,2212],{"class":1260,"line":2160},50,[1258,2162,2125],{"class":1269},[1258,2164,1286],{"class":1273},[1258,2166,963],{"class":1289},[1258,2168,1293],{"class":1273},[1258,2170,2134],{"class":1269},[1258,2172,1274],{"class":1273},[1258,2174,2175],{"class":1269}," orderCompleted",[1258,2177,1274],{"class":1273},[1258,2179,2144],{"class":1269},[1258,2181,1286],{"class":1273},[1258,2183,2149],{"class":1289},[1258,2185,1293],{"class":1273},[1258,2187,2154],{"class":1418},[1258,2189,2190],{"class":1273},"),",[1258,2192,2193],{"class":1269}," duration",[1258,2195,1286],{"class":1273},[1258,2197,2149],{"class":1289},[1258,2199,1293],{"class":1273},[1258,2201,2203],{"class":2202},"sMAmT","250",[1258,2205,2206],{"class":1269},"*time",[1258,2208,1286],{"class":1273},[1258,2210,2211],{"class":1269},"Millisecond",[1258,2213,2157],{"class":1273},[1258,2215,2217],{"class":1260,"line":2216},51,[1258,2218,2219],{"class":1263},"    // → Logged, counted, traced - based on config\n",[1258,2221,2223],{"class":1260,"line":2222},52,[1258,2224,1336],{"emptyLinePlaceholder":1335},[1258,2226,2228,2230,2232,2234],{"class":1260,"line":2227},53,[1258,2229,2125],{"class":1269},[1258,2231,1286],{"class":1273},[1258,2233,891],{"class":1289},[1258,2235,1330],{"class":1273},[1258,2237,2239],{"class":1260,"line":2238},54,[1258,2240,2241],{"class":1273},"}\n",[1164,2243,2244],{},[2245,2246,2247],"strong",{},"observability.yaml:",[1249,2249,2251],{"className":1385,"code":2250,"language":1387,"meta":34,"style":34},"metrics:\n  - signal: order.created\n    name: orders_total\n    type: counter\n  - signal: order.completed\n    name: order_duration_ms\n    type: histogram\n    value_key: duration\n\ntraces:\n  - start: order.created\n    end: order.completed\n    correlation_key: order_id\n    span_name: order_processing\n\nlogs:\n  whitelist:\n    - order.created\n    - order.completed\n",[1255,2252,2253,2259,2269,2277,2285,2295,2304,2313,2323,2327,2333,2343,2351,2359,2367,2371,2377,2383,2389],{"__ignoreMap":34},[1258,2254,2255,2257],{"class":1260,"line":9},[1258,2256,1400],{"class":1399},[1258,2258,1404],{"class":1403},[1258,2260,2261,2263,2265,2267],{"class":1260,"line":19},[1258,2262,1409],{"class":1403},[1258,2264,1412],{"class":1399},[1258,2266,1415],{"class":1403},[1258,2268,1419],{"class":1418},[1258,2270,2271,2273,2275],{"class":1260,"line":40},[1258,2272,1424],{"class":1399},[1258,2274,1415],{"class":1403},[1258,2276,1429],{"class":1418},[1258,2278,2279,2281,2283],{"class":1260,"line":812},[1258,2280,1434],{"class":1399},[1258,2282,1415],{"class":1403},[1258,2284,1439],{"class":1418},[1258,2286,2287,2289,2291,2293],{"class":1260,"line":1339},[1258,2288,1409],{"class":1403},[1258,2290,1412],{"class":1399},[1258,2292,1415],{"class":1403},[1258,2294,1473],{"class":1418},[1258,2296,2297,2299,2301],{"class":1260,"line":1345},[1258,2298,1424],{"class":1399},[1258,2300,1415],{"class":1403},[1258,2302,2303],{"class":1418},"order_duration_ms\n",[1258,2305,2306,2308,2310],{"class":1260,"line":1370},[1258,2307,1434],{"class":1399},[1258,2309,1415],{"class":1403},[1258,2311,2312],{"class":1418},"histogram\n",[1258,2314,2315,2318,2320],{"class":1260,"line":1453},[1258,2316,2317],{"class":1399},"    value_key",[1258,2319,1415],{"class":1403},[1258,2321,2322],{"class":1418},"duration\n",[1258,2324,2325],{"class":1260,"line":1465},[1258,2326,1336],{"emptyLinePlaceholder":1335},[1258,2328,2329,2331],{"class":1260,"line":1476},[1258,2330,1448],{"class":1399},[1258,2332,1404],{"class":1403},[1258,2334,2335,2337,2339,2341],{"class":1260,"line":1487},[1258,2336,1409],{"class":1403},[1258,2338,1458],{"class":1399},[1258,2340,1415],{"class":1403},[1258,2342,1419],{"class":1418},[1258,2344,2345,2347,2349],{"class":1260,"line":1498},[1258,2346,1468],{"class":1399},[1258,2348,1415],{"class":1403},[1258,2350,1473],{"class":1418},[1258,2352,2353,2355,2357],{"class":1260,"line":1503},[1258,2354,1479],{"class":1399},[1258,2356,1415],{"class":1403},[1258,2358,1484],{"class":1418},[1258,2360,2361,2363,2365],{"class":1260,"line":1511},[1258,2362,1490],{"class":1399},[1258,2364,1415],{"class":1403},[1258,2366,1495],{"class":1418},[1258,2368,2369],{"class":1260,"line":1519},[1258,2370,1336],{"emptyLinePlaceholder":1335},[1258,2372,2373,2375],{"class":1260,"line":1527},[1258,2374,1506],{"class":1399},[1258,2376,1404],{"class":1403},[1258,2378,2379,2381],{"class":1260,"line":1677},[1258,2380,1514],{"class":1399},[1258,2382,1404],{"class":1403},[1258,2384,2385,2387],{"class":1260,"line":1704},[1258,2386,1522],{"class":1403},[1258,2388,1419],{"class":1418},[1258,2390,2391,2393],{"class":1260,"line":1727},[1258,2392,1522],{"class":1403},[1258,2394,1473],{"class":1418},[1244,2396,2398],{"id":2397},"why-aperture","Why aperture?",[2400,2401,2402,2409,2415,2421,2433,2439,2445],"ul",{},[2403,2404,2405,2408],"li",{},[2245,2406,2407],{},"Config-driven"," — Change what's observed without recompiling",[2403,2410,2411,2414],{},[2245,2412,2413],{},"Schema-based"," — Load configuration from YAML or JSON",[2403,2416,2417,2420],{},[2245,2418,2419],{},"All three signals"," — Logs, metrics, and traces from a single event stream",[2403,2422,2423,2426,2427,2432],{},[2245,2424,2425],{},"Hot-reloadable"," — Pair with ",[1167,2428,2431],{"href":2429,"rel":2430},"https://github.com/zoobz-io/flux",[1171],"flux"," for live config updates",[2403,2434,2435,2438],{},[2245,2436,2437],{},"Zero instrumentation"," — Domain events become telemetry automatically",[2403,2440,2441,2444],{},[2245,2442,2443],{},"Trace correlation"," — Pair start/end events into spans automatically",[2403,2446,2447,2450],{},[2245,2448,2449],{},"JSON serialization"," — Custom field types automatically serialized",[1244,2452,2454],{"id":2453},"observability-without-instrumentation","Observability Without Instrumentation",[1164,2456,2457,2458,1286],{},"Aperture enables a pattern: ",[2245,2459,2460],{},"emit domain events, configure observability separately",[1164,2462,2463],{},"Your application code emits meaningful domain events through capitan. Aperture transforms those events into OTEL signals based on configuration. Change what's observed — add a metric, remove a trace, adjust log filtering — without touching application code.",[1249,2465,2467],{"className":1251,"code":2466,"language":1253,"meta":34,"style":34},"// Application code stays focused on domain logic\ncapitan.Emit(ctx, orderCreated, orderID.Field(id), total.Field(amount))\ncapitan.Emit(ctx, orderCompleted, orderID.Field(id), duration.Field(elapsed))\n\n// Observability is configured externally\n// → metrics, traces, logs derived from the same event stream\n",[1255,2468,2469,2474,2519,2562,2566,2571],{"__ignoreMap":34},[1258,2470,2471],{"class":1260,"line":9},[1258,2472,2473],{"class":1263},"// Application code stays focused on domain logic\n",[1258,2475,2476,2478,2480,2482,2484,2486,2488,2490,2492,2494,2496,2498,2500,2503,2505,2508,2510,2512,2514,2517],{"class":1260,"line":19},[1258,2477,1238],{"class":1269},[1258,2479,1286],{"class":1273},[1258,2481,963],{"class":1289},[1258,2483,1293],{"class":1273},[1258,2485,2134],{"class":1269},[1258,2487,1274],{"class":1273},[1258,2489,2139],{"class":1269},[1258,2491,1274],{"class":1273},[1258,2493,2144],{"class":1269},[1258,2495,1286],{"class":1273},[1258,2497,2149],{"class":1289},[1258,2499,1293],{"class":1273},[1258,2501,2502],{"class":1269},"id",[1258,2504,2190],{"class":1273},[1258,2506,2507],{"class":1269}," total",[1258,2509,1286],{"class":1273},[1258,2511,2149],{"class":1289},[1258,2513,1293],{"class":1273},[1258,2515,2516],{"class":1269},"amount",[1258,2518,2157],{"class":1273},[1258,2520,2521,2523,2525,2527,2529,2531,2533,2535,2537,2539,2541,2543,2545,2547,2549,2551,2553,2555,2557,2560],{"class":1260,"line":40},[1258,2522,1238],{"class":1269},[1258,2524,1286],{"class":1273},[1258,2526,963],{"class":1289},[1258,2528,1293],{"class":1273},[1258,2530,2134],{"class":1269},[1258,2532,1274],{"class":1273},[1258,2534,2175],{"class":1269},[1258,2536,1274],{"class":1273},[1258,2538,2144],{"class":1269},[1258,2540,1286],{"class":1273},[1258,2542,2149],{"class":1289},[1258,2544,1293],{"class":1273},[1258,2546,2502],{"class":1269},[1258,2548,2190],{"class":1273},[1258,2550,2193],{"class":1269},[1258,2552,1286],{"class":1273},[1258,2554,2149],{"class":1289},[1258,2556,1293],{"class":1273},[1258,2558,2559],{"class":1269},"elapsed",[1258,2561,2157],{"class":1273},[1258,2563,2564],{"class":1260,"line":812},[1258,2565,1336],{"emptyLinePlaceholder":1335},[1258,2567,2568],{"class":1260,"line":1339},[1258,2569,2570],{"class":1263},"// Observability is configured externally\n",[1258,2572,2573],{"class":1260,"line":1345},[1258,2574,2575],{"class":1263},"// → metrics, traces, logs derived from the same event stream\n",[1164,2577,2578],{},"Domain events are stable. Observability requirements change. Keep them separate.",[1244,2580,27],{"id":2581},"capabilities",[2583,2584,2585,2601],"table",{},[2586,2587,2588],"thead",{},[2589,2590,2591,2595,2598],"tr",{},[2592,2593,2594],"th",{},"Feature",[2592,2596,2597],{},"Description",[2592,2599,2600],{},"Docs",[2602,2603,2604,2618,2630,2642,2654,2666],"tbody",{},[2589,2605,2606,2610,2613],{},[2607,2608,2609],"td",{},"Log Filtering",[2607,2611,2612],{},"Whitelist signals for OTEL log records",[2607,2614,2615],{},[1167,2616,133],{"href":2617},"docs/guides/logs",[2589,2619,2620,2622,2625],{},[2607,2621,138],{},[2607,2623,2624],{},"Counters, gauges, histograms, up/down counters from events",[2607,2626,2627],{},[1167,2628,138],{"href":2629},"docs/guides/metrics",[2589,2631,2632,2634,2637],{},[2607,2633,231],{},[2607,2635,2636],{},"Pair start/end events into spans automatically",[2607,2638,2639],{},[1167,2640,143],{"href":2641},"docs/guides/traces",[2589,2643,2644,2646,2649],{},[2607,2645,153],{},[2607,2647,2648],{},"Extract context values as span/log attributes",[2607,2650,2651],{},[1167,2652,607],{"href":2653},"docs/guides/context",[2589,2655,2656,2658,2661],{},[2607,2657,333],{},[2607,2659,2660],{},"Load observability config from YAML or JSON",[2607,2662,2663],{},[1167,2664,198],{"href":2665},"docs/guides/schema",[2589,2667,2668,2671,2674],{},[2607,2669,2670],{},"Testing Utilities",[2607,2672,2673],{},"Mock providers and assertion helpers",[2607,2675,2676],{},[1167,2677,625],{"href":2678},"docs/guides/testing",[1244,2680,2682],{"id":2681},"documentation","Documentation",[1164,2684,2685,2686,2689],{},"Full documentation is available in the ",[1167,2687,2688],{"href":2688},"docs/"," directory:",[2691,2692,1081],"h3",{"id":2693},"learn",[2400,2695,2696,2702,2708],{},[2403,2697,2698,2701],{},[1167,2699,6],{"href":2700},"docs/overview"," — Architecture and philosophy",[2403,2703,2704,2707],{},[1167,2705,53],{"href":2706},"docs/learn/quickstart"," — Get running in five minutes",[2403,2709,2710,2713],{},[1167,2711,91],{"href":2712},"docs/learn/concepts"," — Core mental model",[2691,2715,1093],{"id":2716},"guides",[2400,2718,2719,2724,2729,2734,2739,2744],{},[2403,2720,2721,2723],{},[1167,2722,138],{"href":2629}," — Counters, gauges, histograms, up/down counters",[2403,2725,2726,2728],{},[1167,2727,143],{"href":2641}," — Span correlation from event pairs",[2403,2730,2731,2733],{},[1167,2732,133],{"href":2617}," — Whitelist filtering",[2403,2735,2736,2738],{},[1167,2737,607],{"href":2653}," — Extract context values as attributes",[2403,2740,2741,2743],{},[1167,2742,198],{"href":2665}," — File-based configuration",[2403,2745,2746,2748],{},[1167,2747,625],{"href":2678}," — Test utilities",[2691,2750,1110],{"id":2751},"cookbook",[2400,2753,2754,2761],{},[2403,2755,2756,2760],{},[1167,2757,2759],{"href":2758},"docs/cookbook/http-server","HTTP Server"," — Complete HTTP server example",[2403,2762,2763,2767],{},[1167,2764,2766],{"href":2765},"docs/cookbook/background-workers","Background Workers"," — Job queue observability",[2691,2769,1119],{"id":2770},"reference",[2400,2772,2773],{},[2403,2774,2775,2778],{},[1167,2776,789],{"href":2777},"docs/reference/api"," — Complete API documentation",[1244,2780,2782],{"id":2781},"contributing","Contributing",[1164,2784,2785,2786,2790],{},"See ",[1167,2787,2789],{"href":2788},"CONTRIBUTING","CONTRIBUTING.md"," for guidelines.",[1244,2792,1214],{"id":2793},"license",[1164,2795,2796,2797,2799],{},"MIT License — see ",[1167,2798,1211],{"href":1211}," for details.",[2801,2802,2803],"style",{},"html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",{"title":34,"searchDepth":19,"depth":19,"links":2805},[2806,2807,2808,2809,2810,2811,2812,2818,2819],{"id":1246,"depth":19,"text":1247},{"id":1537,"depth":19,"text":61},{"id":1560,"depth":19,"text":1561},{"id":2397,"depth":19,"text":2398},{"id":2453,"depth":19,"text":2454},{"id":2581,"depth":19,"text":27},{"id":2681,"depth":19,"text":2682,"children":2813},[2814,2815,2816,2817],{"id":2693,"depth":40,"text":1081},{"id":2716,"depth":40,"text":1093},{"id":2751,"depth":40,"text":1110},{"id":2770,"depth":40,"text":1119},{"id":2781,"depth":19,"text":2782},{"id":2793,"depth":19,"text":1214},"md","book-open",{},"/readme",{"title":1155,"description":34},"readme","FatKkAl3-qlESh6OvHUl9yU2gGYPmIZk2xO3Bd_tjOA",{"id":2828,"title":2829,"body":2830,"description":34,"extension":2820,"icon":3245,"meta":3246,"navigation":1335,"path":3247,"seo":3248,"stem":3249,"__hash__":3250},"resources/security.md","Security",{"type":1157,"value":2831,"toc":3231},[2832,2836,2840,2843,2882,2886,2889,2893,2898,2901,2940,2944,2947,2996,3000,3026,3030,3033,3037,3040,3117,3121,3124,3156,3160,3163,3188,3192,3206,3210,3213,3219,3222,3228],[1160,2833,2835],{"id":2834},"security-policy","Security Policy",[1244,2837,2839],{"id":2838},"supported-versions","Supported Versions",[1164,2841,2842],{},"We release patches for security vulnerabilities. Which versions are eligible for receiving such patches depends on the CVSS v3.0 Rating:",[2583,2844,2845,2858],{},[2586,2846,2847],{},[2589,2848,2849,2852,2855],{},[2592,2850,2851],{},"Version",[2592,2853,2854],{},"Supported",[2592,2856,2857],{},"Status",[2602,2859,2860,2871],{},[2589,2861,2862,2865,2868],{},[2607,2863,2864],{},"latest",[2607,2866,2867],{},"✅",[2607,2869,2870],{},"Active development",[2589,2872,2873,2876,2879],{},[2607,2874,2875],{},"\u003C latest",[2607,2877,2878],{},"❌",[2607,2880,2881],{},"Security fixes only for critical issues",[1244,2883,2885],{"id":2884},"reporting-a-vulnerability","Reporting a Vulnerability",[1164,2887,2888],{},"We take the security of aperture seriously. If you have discovered a security vulnerability in this project, please report it responsibly.",[2691,2890,2892],{"id":2891},"how-to-report","How to Report",[1164,2894,2895],{},[2245,2896,2897],{},"Please DO NOT report security vulnerabilities through public GitHub issues.",[1164,2899,2900],{},"Instead, please report them via one of the following methods:",[2902,2903,2904,2927],"ol",{},[2403,2905,2906,2909,2910],{},[2245,2907,2908],{},"GitHub Security Advisories"," (Preferred)",[2400,2911,2912,2921,2924],{},[2403,2913,2914,2915,2920],{},"Go to the ",[1167,2916,2919],{"href":2917,"rel":2918},"https://github.com/zoobz-io/aperture/security",[1171],"Security tab"," of this repository",[2403,2922,2923],{},"Click \"Report a vulnerability\"",[2403,2925,2926],{},"Fill out the form with details about the vulnerability",[2403,2928,2929,2932],{},[2245,2930,2931],{},"Email",[2400,2933,2934,2937],{},[2403,2935,2936],{},"Send details to the repository maintainer through GitHub profile contact information",[2403,2938,2939],{},"Use PGP encryption if possible for sensitive details",[2691,2941,2943],{"id":2942},"what-to-include","What to Include",[1164,2945,2946],{},"Please include the following information (as much as you can provide) to help us better understand the nature and scope of the possible issue:",[2400,2948,2949,2955,2961,2967,2973,2978,2984,2990],{},[2403,2950,2951,2954],{},[2245,2952,2953],{},"Type of issue"," (e.g., buffer overflow, SQL injection, cross-site scripting, etc.)",[2403,2956,2957,2960],{},[2245,2958,2959],{},"Full paths of source file(s)"," related to the manifestation of the issue",[2403,2962,2963,2966],{},[2245,2964,2965],{},"The location of the affected source code"," (tag/branch/commit or direct URL)",[2403,2968,2969,2972],{},[2245,2970,2971],{},"Any special configuration required"," to reproduce the issue",[2403,2974,2975,2972],{},[2245,2976,2977],{},"Step-by-step instructions",[2403,2979,2980,2983],{},[2245,2981,2982],{},"Proof-of-concept or exploit code"," (if possible)",[2403,2985,2986,2989],{},[2245,2987,2988],{},"Impact of the issue",", including how an attacker might exploit the issue",[2403,2991,2992,2995],{},[2245,2993,2994],{},"Your name and affiliation"," (optional)",[2691,2997,2999],{"id":2998},"what-to-expect","What to Expect",[2400,3001,3002,3008,3014,3020],{},[2403,3003,3004,3007],{},[2245,3005,3006],{},"Acknowledgment",": We will acknowledge receipt of your vulnerability report within 48 hours",[2403,3009,3010,3013],{},[2245,3011,3012],{},"Initial Assessment",": Within 7 days, we will provide an initial assessment of the report",[2403,3015,3016,3019],{},[2245,3017,3018],{},"Resolution Timeline",": We aim to resolve critical issues within 30 days",[2403,3021,3022,3025],{},[2245,3023,3024],{},"Disclosure",": We will coordinate with you on the disclosure timeline",[2691,3027,3029],{"id":3028},"preferred-languages","Preferred Languages",[1164,3031,3032],{},"We prefer all communications to be in English.",[1244,3034,3036],{"id":3035},"security-best-practices","Security Best Practices",[1164,3038,3039],{},"When using aperture in your applications, we recommend:",[2902,3041,3042,3063,3076,3088,3101],{},[2403,3043,3044,3047],{},[2245,3045,3046],{},"Keep Dependencies Updated",[1249,3048,3050],{"className":1540,"code":3049,"language":1542,"meta":34,"style":34},"go get -u github.com/zoobz-io/aperture\n",[1255,3051,3052],{"__ignoreMap":34},[1258,3053,3054,3056,3058,3061],{"class":1260,"line":9},[1258,3055,1253],{"class":1289},[1258,3057,1551],{"class":1418},[1258,3059,3060],{"class":1399}," -u",[1258,3062,1554],{"class":1418},[2403,3064,3065,3068],{},[2245,3066,3067],{},"Use Context Properly",[2400,3069,3070,3073],{},[2403,3071,3072],{},"Always pass contexts with appropriate timeouts",[2403,3074,3075],{},"Handle context cancellation in your processors",[2403,3077,3078,3080],{},[2245,3079,260],{},[2400,3081,3082,3085],{},[2403,3083,3084],{},"Never ignore errors returned by pipelines",[2403,3086,3087],{},"Implement proper fallback mechanisms",[2403,3089,3090,3093],{},[2245,3091,3092],{},"Input Validation",[2400,3094,3095,3098],{},[2403,3096,3097],{},"Validate all inputs before processing",[2403,3099,3100],{},"Use the Filter processor to sanitize data",[2403,3102,3103,3106],{},[2245,3104,3105],{},"Resource Management",[2400,3107,3108,3111,3114],{},[2403,3109,3110],{},"Use rate limiters for external API calls",[2403,3112,3113],{},"Implement circuit breakers for failing services",[2403,3115,3116],{},"Set appropriate timeouts for all operations",[1244,3118,3120],{"id":3119},"security-features","Security Features",[1164,3122,3123],{},"aperture includes several built-in security features:",[2400,3125,3126,3132,3138,3144,3150],{},[2403,3127,3128,3131],{},[2245,3129,3130],{},"Type Safety",": Generic types prevent type confusion attacks",[2403,3133,3134,3137],{},[2245,3135,3136],{},"Context Support",": Built-in cancellation and timeout support",[2403,3139,3140,3143],{},[2245,3141,3142],{},"Error Isolation",": Errors are properly wrapped and traced",[2403,3145,3146,3149],{},[2245,3147,3148],{},"Resource Controls",": Rate limiting and circuit breaker patterns",[2403,3151,3152,3155],{},[2245,3153,3154],{},"No Dependencies",": Zero external dependencies reduce attack surface",[1244,3157,3159],{"id":3158},"automated-security-scanning","Automated Security Scanning",[1164,3161,3162],{},"This project uses:",[2400,3164,3165,3170,3176,3182],{},[2403,3166,3167,3169],{},[2245,3168,1199],{},": GitHub's semantic code analysis for security vulnerabilities",[2403,3171,3172,3175],{},[2245,3173,3174],{},"Dependabot",": Automated dependency updates (for dev dependencies)",[2403,3177,3178,3181],{},[2245,3179,3180],{},"golangci-lint",": Static analysis including security linters",[2403,3183,3184,3187],{},[2245,3185,3186],{},"Codecov",": Coverage tracking to ensure security-critical code is tested",[1244,3189,3191],{"id":3190},"vulnerability-disclosure-policy","Vulnerability Disclosure Policy",[2400,3193,3194,3197,3200,3203],{},[2403,3195,3196],{},"Security vulnerabilities will be disclosed via GitHub Security Advisories",[2403,3198,3199],{},"We follow a 90-day disclosure timeline for non-critical issues",[2403,3201,3202],{},"Critical vulnerabilities may be disclosed sooner after patches are available",[2403,3204,3205],{},"We will credit reporters who follow responsible disclosure practices",[1244,3207,3209],{"id":3208},"credits","Credits",[1164,3211,3212],{},"We thank the following individuals for responsibly disclosing security issues:",[1164,3214,3215],{},[3216,3217,3218],"em",{},"This list is currently empty. Be the first to help improve our security!",[3220,3221],"hr",{},[1164,3223,3224,3227],{},[2245,3225,3226],{},"Last Updated",": 2025-08-19",[2801,3229,3230],{},"html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":34,"searchDepth":19,"depth":19,"links":3232},[3233,3234,3240,3241,3242,3243,3244],{"id":2838,"depth":19,"text":2839},{"id":2884,"depth":19,"text":2885,"children":3235},[3236,3237,3238,3239],{"id":2891,"depth":40,"text":2892},{"id":2942,"depth":40,"text":2943},{"id":2998,"depth":40,"text":2999},{"id":3028,"depth":40,"text":3029},{"id":3035,"depth":19,"text":3036},{"id":3119,"depth":19,"text":3120},{"id":3158,"depth":19,"text":3159},{"id":3190,"depth":19,"text":3191},{"id":3208,"depth":19,"text":3209},"shield",{},"/security",{"title":2829,"description":34},"security","A7-NXCQmMFikOSKbrC5eCiKgPMXLG_5WnXVGbMujN4k",{"id":3252,"title":2782,"body":3253,"description":3261,"extension":2820,"icon":1255,"meta":3838,"navigation":1335,"path":3839,"seo":3840,"stem":2781,"__hash__":3841},"resources/contributing.md",{"type":1157,"value":3254,"toc":3812},[3255,3259,3262,3266,3269,3273,3311,3315,3319,3337,3340,3356,3358,3369,3373,3377,3391,3395,3406,3410,3415,3418,3432,3436,3439,3457,3461,3464,3478,3482,3510,3513,3516,3531,3534,3550,3553,3569,3573,3581,3585,3588,3632,3636,3640,3643,3654,3657,3671,3675,3678,3706,3710,3736,3740,3767,3773,3777,3780,3791,3795,3806,3809],[1160,3256,3258],{"id":3257},"contributing-to-aperture","Contributing to aperture",[1164,3260,3261],{},"Thank you for your interest in contributing to aperture! This guide will help you get started.",[1244,3263,3265],{"id":3264},"code-of-conduct","Code of Conduct",[1164,3267,3268],{},"By participating in this project, you agree to maintain a respectful and inclusive environment for all contributors.",[1244,3270,3272],{"id":3271},"getting-started","Getting Started",[2902,3274,3275,3278,3284,3290,3293,3299,3302,3308],{},[2403,3276,3277],{},"Fork the repository",[2403,3279,3280,3281],{},"Clone your fork: ",[1255,3282,3283],{},"git clone https://github.com/yourusername/aperture.git",[2403,3285,3286,3287],{},"Create a feature branch: ",[1255,3288,3289],{},"git checkout -b feature/your-feature-name",[2403,3291,3292],{},"Make your changes",[2403,3294,3295,3296],{},"Run tests: ",[1255,3297,3298],{},"go test ./...",[2403,3300,3301],{},"Commit your changes with a descriptive message",[2403,3303,3304,3305],{},"Push to your fork: ",[1255,3306,3307],{},"git push origin feature/your-feature-name",[2403,3309,3310],{},"Create a Pull Request",[1244,3312,3314],{"id":3313},"development-guidelines","Development Guidelines",[2691,3316,3318],{"id":3317},"code-style","Code Style",[2400,3320,3321,3324,3331,3334],{},[2403,3322,3323],{},"Follow standard Go conventions",[2403,3325,3326,3327,3330],{},"Run ",[1255,3328,3329],{},"go fmt"," before committing",[2403,3332,3333],{},"Add comments for exported functions and types",[2403,3335,3336],{},"Keep functions small and focused",[2691,3338,625],{"id":3339},"testing",[2400,3341,3342,3345,3350,3353],{},[2403,3343,3344],{},"Write tests for new functionality",[2403,3346,3347,3348],{},"Ensure all tests pass: ",[1255,3349,3298],{},[2403,3351,3352],{},"Include benchmarks for performance-critical code",[2403,3354,3355],{},"Aim for good test coverage",[2691,3357,2682],{"id":2681},[2400,3359,3360,3363,3366],{},[2403,3361,3362],{},"Update documentation for API changes",[2403,3364,3365],{},"Add examples for new features",[2403,3367,3368],{},"Keep doc comments clear and concise",[1244,3370,3372],{"id":3371},"types-of-contributions","Types of Contributions",[2691,3374,3376],{"id":3375},"bug-reports","Bug Reports",[2400,3378,3379,3382,3385,3388],{},[2403,3380,3381],{},"Use GitHub Issues",[2403,3383,3384],{},"Include minimal reproduction code",[2403,3386,3387],{},"Describe expected vs actual behavior",[2403,3389,3390],{},"Include Go version and OS",[2691,3392,3394],{"id":3393},"feature-requests","Feature Requests",[2400,3396,3397,3400,3403],{},[2403,3398,3399],{},"Open an issue for discussion first",[2403,3401,3402],{},"Explain the use case",[2403,3404,3405],{},"Consider backwards compatibility",[2691,3407,3409],{"id":3408},"code-contributions","Code Contributions",[3411,3412,3414],"h4",{"id":3413},"core-functionality","Core Functionality",[1164,3416,3417],{},"New features should:",[2400,3419,3420,3423,3426,3429],{},[2403,3421,3422],{},"Follow existing patterns and conventions",[2403,3424,3425],{},"Include comprehensive tests",[2403,3427,3428],{},"Add documentation with examples",[2403,3430,3431],{},"Maintain separation of concerns between core and providers",[3411,3433,3435],{"id":3434},"custom-transformers","Custom Transformers",[1164,3437,3438],{},"New field transformers should:",[2400,3440,3441,3448,3451,3454],{},[2403,3442,3443,3444,3447],{},"Use ",[1255,3445,3446],{},"RegisterTransformer()"," properly",[2403,3449,3450],{},"Handle type assertions safely",[2403,3452,3453],{},"Include tests for edge cases",[2403,3455,3456],{},"Document behavior clearly",[3411,3458,3460],{"id":3459},"examples","Examples",[1164,3462,3463],{},"New examples should:",[2400,3465,3466,3469,3472,3475],{},[2403,3467,3468],{},"Demonstrate real-world use cases",[2403,3470,3471],{},"Include tests and benchmarks",[2403,3473,3474],{},"Have a descriptive README",[2403,3476,3477],{},"Follow the existing structure",[1244,3479,3481],{"id":3480},"pull-request-process","Pull Request Process",[2902,3483,3484,3490,3495,3500,3505],{},[2403,3485,3486,3489],{},[2245,3487,3488],{},"Keep PRs focused"," - One feature/fix per PR",[2403,3491,3492],{},[2245,3493,3494],{},"Write descriptive commit messages",[2403,3496,3497],{},[2245,3498,3499],{},"Update tests and documentation",[2403,3501,3502],{},[2245,3503,3504],{},"Ensure CI passes",[2403,3506,3507],{},[2245,3508,3509],{},"Respond to review feedback",[1244,3511,625],{"id":3512},"testing-1",[1164,3514,3515],{},"Run the full test suite:",[1249,3517,3519],{"className":1540,"code":3518,"language":1542,"meta":34,"style":34},"go test ./...\n",[1255,3520,3521],{"__ignoreMap":34},[1258,3522,3523,3525,3528],{"class":1260,"line":9},[1258,3524,1253],{"class":1289},[1258,3526,3527],{"class":1418}," test",[1258,3529,3530],{"class":1418}," ./...\n",[1164,3532,3533],{},"Run with race detection:",[1249,3535,3537],{"className":1540,"code":3536,"language":1542,"meta":34,"style":34},"go test -race ./...\n",[1255,3538,3539],{"__ignoreMap":34},[1258,3540,3541,3543,3545,3548],{"class":1260,"line":9},[1258,3542,1253],{"class":1289},[1258,3544,3527],{"class":1418},[1258,3546,3547],{"class":1399}," -race",[1258,3549,3530],{"class":1418},[1164,3551,3552],{},"Run benchmarks:",[1249,3554,3556],{"className":1540,"code":3555,"language":1542,"meta":34,"style":34},"go test -bench=. ./...\n",[1255,3557,3558],{"__ignoreMap":34},[1258,3559,3560,3562,3564,3567],{"class":1260,"line":9},[1258,3561,1253],{"class":1289},[1258,3563,3527],{"class":1418},[1258,3565,3566],{"class":1399}," -bench=.",[1258,3568,3530],{"class":1418},[1244,3570,3572],{"id":3571},"project-structure","Project Structure",[1249,3574,3579],{"className":3575,"code":3577,"language":3578},[3576],"language-text","aperture/\n├── *.go              # Core library files\n├── *_test.go         # Tests\n├── providers/        # OTEL provider configuration\n│   └── providers.go  # Default provider setup\n├── .github/          # GitHub workflows and automation\n└── docs/             # Documentation\n","text",[1255,3580,3577],{"__ignoreMap":34},[1244,3582,3584],{"id":3583},"commit-messages","Commit Messages",[1164,3586,3587],{},"Follow conventional commits:",[2400,3589,3590,3596,3602,3608,3614,3620,3626],{},[2403,3591,3592,3595],{},[1255,3593,3594],{},"feat:"," New feature",[2403,3597,3598,3601],{},[1255,3599,3600],{},"fix:"," Bug fix",[2403,3603,3604,3607],{},[1255,3605,3606],{},"docs:"," Documentation changes",[2403,3609,3610,3613],{},[1255,3611,3612],{},"test:"," Test additions/changes",[2403,3615,3616,3619],{},[1255,3617,3618],{},"refactor:"," Code refactoring",[2403,3621,3622,3625],{},[1255,3623,3624],{},"perf:"," Performance improvements",[2403,3627,3628,3631],{},[1255,3629,3630],{},"chore:"," Maintenance tasks",[1244,3633,3635],{"id":3634},"release-process","Release Process",[2691,3637,3639],{"id":3638},"automated-releases","Automated Releases",[1164,3641,3642],{},"This project uses automated release versioning. To create a release:",[2902,3644,3645,3648,3651],{},[2403,3646,3647],{},"Go to Actions → Release → Run workflow",[2403,3649,3650],{},"Leave \"Version override\" empty for automatic version inference",[2403,3652,3653],{},"Click \"Run workflow\"",[1164,3655,3656],{},"The system will:",[2400,3658,3659,3662,3665,3668],{},[2403,3660,3661],{},"Automatically determine the next version from conventional commits",[2403,3663,3664],{},"Create a git tag",[2403,3666,3667],{},"Generate release notes via GoReleaser",[2403,3669,3670],{},"Publish the release to GitHub",[2691,3672,3674],{"id":3673},"manual-release-legacy","Manual Release (Legacy)",[1164,3676,3677],{},"You can still create releases manually:",[1249,3679,3681],{"className":1540,"code":3680,"language":1542,"meta":34,"style":34},"git tag v1.2.3\ngit push origin v1.2.3\n",[1255,3682,3683,3694],{"__ignoreMap":34},[1258,3684,3685,3688,3691],{"class":1260,"line":9},[1258,3686,3687],{"class":1289},"git",[1258,3689,3690],{"class":1418}," tag",[1258,3692,3693],{"class":1418}," v1.2.3\n",[1258,3695,3696,3698,3701,3704],{"class":1260,"line":19},[1258,3697,3687],{"class":1289},[1258,3699,3700],{"class":1418}," push",[1258,3702,3703],{"class":1418}," origin",[1258,3705,3693],{"class":1418},[2691,3707,3709],{"id":3708},"known-limitations","Known Limitations",[2400,3711,3712,3718,3724],{},[2403,3713,3714,3717],{},[2245,3715,3716],{},"Protected branches",": The automated release cannot bypass branch protection rules. This is by design for security.",[2403,3719,3720,3723],{},[2245,3721,3722],{},"Concurrent releases",": Rapid successive releases may fail. Simply retry after a moment.",[2403,3725,3726,3729,3730,3732,3733,3735],{},[2245,3727,3728],{},"Conventional commits required",": Version inference requires conventional commit format (",[1255,3731,3594],{},", ",[1255,3734,3600],{},", etc.)",[2691,3737,3739],{"id":3738},"commit-conventions-for-versioning","Commit Conventions for Versioning",[2400,3741,3742,3747,3752,3758],{},[2403,3743,3744,3746],{},[1255,3745,3594],{}," new features (minor version: 1.2.0 → 1.3.0)",[2403,3748,3749,3751],{},[1255,3750,3600],{}," bug fixes (patch version: 1.2.0 → 1.2.1)",[2403,3753,3754,3757],{},[1255,3755,3756],{},"feat!:"," breaking changes (major version: 1.2.0 → 2.0.0)",[2403,3759,3760,3732,3762,3732,3764,3766],{},[1255,3761,3606],{},[1255,3763,3612],{},[1255,3765,3630],{}," no version change",[1164,3768,3769,3770],{},"Example: ",[1255,3771,3772],{},"feat(metrics): add support for exponential histograms",[2691,3774,3776],{"id":3775},"version-preview-on-pull-requests","Version Preview on Pull Requests",[1164,3778,3779],{},"Every PR automatically shows the next version that will be created:",[2400,3781,3782,3785,3788],{},[2403,3783,3784],{},"Check PR comments for \"Version Preview\"",[2403,3786,3787],{},"Updates automatically as you add commits",[2403,3789,3790],{},"Helps verify your commits have the intended effect",[1244,3792,3794],{"id":3793},"questions","Questions?",[2400,3796,3797,3800,3803],{},[2403,3798,3799],{},"Open an issue for questions",[2403,3801,3802],{},"Check existing issues first",[2403,3804,3805],{},"Be patient and respectful",[1164,3807,3808],{},"Thank you for contributing to aperture!",[2801,3810,3811],{},"html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}",{"title":34,"searchDepth":19,"depth":19,"links":3813},[3814,3815,3816,3821,3826,3827,3828,3829,3830,3837],{"id":3264,"depth":19,"text":3265},{"id":3271,"depth":19,"text":3272},{"id":3313,"depth":19,"text":3314,"children":3817},[3818,3819,3820],{"id":3317,"depth":40,"text":3318},{"id":3339,"depth":40,"text":625},{"id":2681,"depth":40,"text":2682},{"id":3371,"depth":19,"text":3372,"children":3822},[3823,3824,3825],{"id":3375,"depth":40,"text":3376},{"id":3393,"depth":40,"text":3394},{"id":3408,"depth":40,"text":3409},{"id":3480,"depth":19,"text":3481},{"id":3512,"depth":19,"text":625},{"id":3571,"depth":19,"text":3572},{"id":3583,"depth":19,"text":3584},{"id":3634,"depth":19,"text":3635,"children":3831},[3832,3833,3834,3835,3836],{"id":3638,"depth":40,"text":3639},{"id":3673,"depth":40,"text":3674},{"id":3708,"depth":40,"text":3709},{"id":3738,"depth":40,"text":3739},{"id":3775,"depth":40,"text":3776},{"id":3793,"depth":19,"text":3794},{},"/contributing",{"title":2782,"description":3261},"wdWHaDMmnePR6_Ks36tuGDN4bknoZgmu56ue5SY1pNc",{"id":3843,"title":53,"author":3844,"body":3845,"description":55,"extension":2820,"meta":5305,"navigation":1335,"path":52,"published":5306,"readtime":5307,"seo":5308,"stem":1086,"tags":5309,"updated":5310,"__hash__":5311},"aperture/v1.0.3/2.learn/1.quickstart.md","zoobzio",{"type":1157,"value":3846,"toc":5297},[3847,3850,3852,3864,3870,3873,4505,4508,4534,4537,4540,4543,5084,5087,5090,5265,5268,5294],[1160,3848,53],{"id":3849},"quickstart",[1244,3851,61],{"id":1537},[1249,3853,3854],{"className":1540,"code":1541,"language":1542,"meta":34,"style":34},[1255,3855,3856],{"__ignoreMap":34},[1258,3857,3858,3860,3862],{"class":1260,"line":9},[1258,3859,1253],{"class":1289},[1258,3861,1551],{"class":1418},[1258,3863,1554],{"class":1418},[1164,3865,3866,3867,1286],{},"Requires Go 1.24+ and ",[1167,3868,1238],{"href":1236,"rel":3869},[1171],[1244,3871,66],{"id":3872},"minimal-setup",[1249,3874,3876],{"className":1251,"code":3875,"language":1253,"meta":34,"style":34},"package main\n\nimport (\n    \"context\"\n    \"time\"\n\n    \"github.com/zoobz-io/aperture\"\n    \"github.com/zoobz-io/capitan\"\n    \"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp\"\n    \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp\"\n    \"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp\"\n    \"go.opentelemetry.io/otel/sdk/log\"\n    \"go.opentelemetry.io/otel/sdk/metric\"\n    \"go.opentelemetry.io/otel/sdk/trace\"\n)\n\nfunc main() {\n    ctx := context.Background()\n\n    // 1. Create OTEL providers\n    logExporter, _ := otlploghttp.New(ctx, otlploghttp.WithEndpoint(\"localhost:4318\"), otlploghttp.WithInsecure())\n    logProvider := log.NewLoggerProvider(log.WithProcessor(log.NewBatchProcessor(logExporter)))\n    defer logProvider.Shutdown(ctx)\n\n    metricExporter, _ := otlpmetrichttp.New(ctx, otlpmetrichttp.WithEndpoint(\"localhost:4318\"), otlpmetrichttp.WithInsecure())\n    meterProvider := metric.NewMeterProvider(metric.WithReader(\n        metric.NewPeriodicReader(metricExporter, metric.WithInterval(60*time.Second)),\n    ))\n    defer meterProvider.Shutdown(ctx)\n\n    traceExporter, _ := otlptracehttp.New(ctx, otlptracehttp.WithEndpoint(\"localhost:4318\"), otlptracehttp.WithInsecure())\n    traceProvider := trace.NewTracerProvider(trace.WithSpanProcessor(trace.NewBatchSpanProcessor(traceExporter)))\n    defer traceProvider.Shutdown(ctx)\n\n    // 2. Create aperture (no config = log all events)\n    cap := capitan.Default()\n    ap, err := aperture.New(cap, logProvider, meterProvider, traceProvider)\n    if err != nil {\n        panic(err)\n    }\n    defer ap.Close()\n\n    // 3. Emit events - they automatically become OTEL logs\n    sig := capitan.NewSignal(\"app.started\", \"Application started\")\n    cap.Emit(ctx, sig)\n\n    // 4. Graceful shutdown\n    cap.Shutdown()\n}\n",[1255,3877,3878,3884,3888,3894,3898,3902,3906,3910,3914,3919,3924,3929,3934,3939,3944,3948,3952,3962,3976,3980,3985,4033,4074,4090,4094,4138,4166,4205,4210,4226,4230,4274,4315,4331,4335,4340,4355,4389,4401,4413,4417,4429,4433,4438,4463,4482,4486,4491,4501],{"__ignoreMap":34},[1258,3879,3880,3882],{"class":1260,"line":9},[1258,3881,1571],{"class":1399},[1258,3883,1575],{"class":1574},[1258,3885,3886],{"class":1260,"line":19},[1258,3887,1336],{"emptyLinePlaceholder":1335},[1258,3889,3890,3892],{"class":1260,"line":40},[1258,3891,1584],{"class":1399},[1258,3893,1587],{"class":1403},[1258,3895,3896],{"class":1260,"line":812},[1258,3897,1592],{"class":1418},[1258,3899,3900],{"class":1260,"line":1339},[1258,3901,1607],{"class":1418},[1258,3903,3904],{"class":1260,"line":1345},[1258,3905,1336],{"emptyLinePlaceholder":1335},[1258,3907,3908],{"class":1260,"line":1370},[1258,3909,1616],{"class":1418},[1258,3911,3912],{"class":1260,"line":1453},[1258,3913,1621],{"class":1418},[1258,3915,3916],{"class":1260,"line":1465},[1258,3917,3918],{"class":1418},"    \"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp\"\n",[1258,3920,3921],{"class":1260,"line":1476},[1258,3922,3923],{"class":1418},"    \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp\"\n",[1258,3925,3926],{"class":1260,"line":1487},[1258,3927,3928],{"class":1418},"    \"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp\"\n",[1258,3930,3931],{"class":1260,"line":1498},[1258,3932,3933],{"class":1418},"    \"go.opentelemetry.io/otel/sdk/log\"\n",[1258,3935,3936],{"class":1260,"line":1503},[1258,3937,3938],{"class":1418},"    \"go.opentelemetry.io/otel/sdk/metric\"\n",[1258,3940,3941],{"class":1260,"line":1511},[1258,3942,3943],{"class":1418},"    \"go.opentelemetry.io/otel/sdk/trace\"\n",[1258,3945,3946],{"class":1260,"line":1519},[1258,3947,1314],{"class":1403},[1258,3949,3950],{"class":1260,"line":1527},[1258,3951,1336],{"emptyLinePlaceholder":1335},[1258,3953,3954,3956,3958,3960],{"class":1260,"line":1677},[1258,3955,1763],{"class":1399},[1258,3957,1766],{"class":1289},[1258,3959,1769],{"class":1273},[1258,3961,1772],{"class":1273},[1258,3963,3964,3966,3968,3970,3972,3974],{"class":1260,"line":1704},[1258,3965,1778],{"class":1269},[1258,3967,1280],{"class":1269},[1258,3969,1783],{"class":1269},[1258,3971,1286],{"class":1273},[1258,3973,1788],{"class":1289},[1258,3975,1330],{"class":1273},[1258,3977,3978],{"class":1260,"line":1727},[1258,3979,1336],{"emptyLinePlaceholder":1335},[1258,3981,3982],{"class":1260,"line":1750},[1258,3983,3984],{"class":1263},"    // 1. Create OTEL providers\n",[1258,3986,3987,3990,3992,3994,3996,3999,4001,4003,4005,4007,4009,4011,4013,4016,4018,4021,4023,4025,4027,4030],{"class":1260,"line":1755},[1258,3988,3989],{"class":1269},"    logExporter",[1258,3991,1274],{"class":1273},[1258,3993,1277],{"class":1269},[1258,3995,1280],{"class":1269},[1258,3997,3998],{"class":1269}," otlploghttp",[1258,4000,1286],{"class":1273},[1258,4002,1290],{"class":1289},[1258,4004,1293],{"class":1273},[1258,4006,2134],{"class":1269},[1258,4008,1274],{"class":1273},[1258,4010,3998],{"class":1269},[1258,4012,1286],{"class":1273},[1258,4014,4015],{"class":1289},"WithEndpoint",[1258,4017,1293],{"class":1273},[1258,4019,4020],{"class":1418},"\"localhost:4318\"",[1258,4022,2190],{"class":1273},[1258,4024,3998],{"class":1269},[1258,4026,1286],{"class":1273},[1258,4028,4029],{"class":1289},"WithInsecure",[1258,4031,4032],{"class":1273},"())\n",[1258,4034,4035,4037,4039,4042,4044,4047,4049,4052,4054,4057,4059,4061,4063,4066,4068,4071],{"class":1260,"line":1760},[1258,4036,1807],{"class":1269},[1258,4038,1280],{"class":1269},[1258,4040,4041],{"class":1269}," log",[1258,4043,1286],{"class":1273},[1258,4045,4046],{"class":1289},"NewLoggerProvider",[1258,4048,1293],{"class":1273},[1258,4050,4051],{"class":1269},"log",[1258,4053,1286],{"class":1273},[1258,4055,4056],{"class":1289},"WithProcessor",[1258,4058,1293],{"class":1273},[1258,4060,4051],{"class":1269},[1258,4062,1286],{"class":1273},[1258,4064,4065],{"class":1289},"NewBatchProcessor",[1258,4067,1293],{"class":1273},[1258,4069,4070],{"class":1269},"logExporter",[1258,4072,4073],{"class":1273},")))\n",[1258,4075,4076,4078,4080,4082,4084,4086,4088],{"class":1260,"line":1775},[1258,4077,1922],{"class":1319},[1258,4079,1301],{"class":1269},[1258,4081,1286],{"class":1273},[1258,4083,891],{"class":1289},[1258,4085,1293],{"class":1273},[1258,4087,2134],{"class":1269},[1258,4089,1314],{"class":1273},[1258,4091,4092],{"class":1260,"line":1793},[1258,4093,1336],{"emptyLinePlaceholder":1335},[1258,4095,4096,4099,4101,4103,4105,4108,4110,4112,4114,4116,4118,4120,4122,4124,4126,4128,4130,4132,4134,4136],{"class":1260,"line":1798},[1258,4097,4098],{"class":1269},"    metricExporter",[1258,4100,1274],{"class":1273},[1258,4102,1277],{"class":1269},[1258,4104,1280],{"class":1269},[1258,4106,4107],{"class":1269}," otlpmetrichttp",[1258,4109,1286],{"class":1273},[1258,4111,1290],{"class":1289},[1258,4113,1293],{"class":1273},[1258,4115,2134],{"class":1269},[1258,4117,1274],{"class":1273},[1258,4119,4107],{"class":1269},[1258,4121,1286],{"class":1273},[1258,4123,4015],{"class":1289},[1258,4125,1293],{"class":1273},[1258,4127,4020],{"class":1418},[1258,4129,2190],{"class":1273},[1258,4131,4107],{"class":1269},[1258,4133,1286],{"class":1273},[1258,4135,4029],{"class":1289},[1258,4137,4032],{"class":1273},[1258,4139,4140,4143,4145,4148,4150,4153,4155,4158,4160,4163],{"class":1260,"line":1804},[1258,4141,4142],{"class":1269},"    meterProvider",[1258,4144,1280],{"class":1269},[1258,4146,4147],{"class":1269}," metric",[1258,4149,1286],{"class":1273},[1258,4151,4152],{"class":1289},"NewMeterProvider",[1258,4154,1293],{"class":1273},[1258,4156,4157],{"class":1269},"metric",[1258,4159,1286],{"class":1273},[1258,4161,4162],{"class":1289},"WithReader",[1258,4164,4165],{"class":1273},"(\n",[1258,4167,4168,4171,4173,4176,4178,4181,4183,4185,4187,4190,4192,4195,4197,4199,4202],{"class":1260,"line":1825},[1258,4169,4170],{"class":1269},"        metric",[1258,4172,1286],{"class":1273},[1258,4174,4175],{"class":1289},"NewPeriodicReader",[1258,4177,1293],{"class":1273},[1258,4179,4180],{"class":1269},"metricExporter",[1258,4182,1274],{"class":1273},[1258,4184,4147],{"class":1269},[1258,4186,1286],{"class":1273},[1258,4188,4189],{"class":1289},"WithInterval",[1258,4191,1293],{"class":1273},[1258,4193,4194],{"class":2202},"60",[1258,4196,2206],{"class":1269},[1258,4198,1286],{"class":1273},[1258,4200,4201],{"class":1269},"Second",[1258,4203,4204],{"class":1273},")),\n",[1258,4206,4207],{"class":1260,"line":1830},[1258,4208,4209],{"class":1273},"    ))\n",[1258,4211,4212,4214,4216,4218,4220,4222,4224],{"class":1260,"line":1836},[1258,4213,1922],{"class":1319},[1258,4215,1306],{"class":1269},[1258,4217,1286],{"class":1273},[1258,4219,891],{"class":1289},[1258,4221,1293],{"class":1273},[1258,4223,2134],{"class":1269},[1258,4225,1314],{"class":1273},[1258,4227,4228],{"class":1260,"line":1879},[1258,4229,1336],{"emptyLinePlaceholder":1335},[1258,4231,4232,4235,4237,4239,4241,4244,4246,4248,4250,4252,4254,4256,4258,4260,4262,4264,4266,4268,4270,4272],{"class":1260,"line":1895},[1258,4233,4234],{"class":1269},"    traceExporter",[1258,4236,1274],{"class":1273},[1258,4238,1277],{"class":1269},[1258,4240,1280],{"class":1269},[1258,4242,4243],{"class":1269}," otlptracehttp",[1258,4245,1286],{"class":1273},[1258,4247,1290],{"class":1289},[1258,4249,1293],{"class":1273},[1258,4251,2134],{"class":1269},[1258,4253,1274],{"class":1273},[1258,4255,4243],{"class":1269},[1258,4257,1286],{"class":1273},[1258,4259,4015],{"class":1289},[1258,4261,1293],{"class":1273},[1258,4263,4020],{"class":1418},[1258,4265,2190],{"class":1273},[1258,4267,4243],{"class":1269},[1258,4269,1286],{"class":1273},[1258,4271,4029],{"class":1289},[1258,4273,4032],{"class":1273},[1258,4275,4276,4279,4281,4284,4286,4289,4291,4294,4296,4299,4301,4303,4305,4308,4310,4313],{"class":1260,"line":1913},[1258,4277,4278],{"class":1269},"    traceProvider",[1258,4280,1280],{"class":1269},[1258,4282,4283],{"class":1269}," trace",[1258,4285,1286],{"class":1273},[1258,4287,4288],{"class":1289},"NewTracerProvider",[1258,4290,1293],{"class":1273},[1258,4292,4293],{"class":1269},"trace",[1258,4295,1286],{"class":1273},[1258,4297,4298],{"class":1289},"WithSpanProcessor",[1258,4300,1293],{"class":1273},[1258,4302,4293],{"class":1269},[1258,4304,1286],{"class":1273},[1258,4306,4307],{"class":1289},"NewBatchSpanProcessor",[1258,4309,1293],{"class":1273},[1258,4311,4312],{"class":1269},"traceExporter",[1258,4314,4073],{"class":1273},[1258,4316,4317,4319,4321,4323,4325,4327,4329],{"class":1260,"line":1919},[1258,4318,1922],{"class":1319},[1258,4320,1311],{"class":1269},[1258,4322,1286],{"class":1273},[1258,4324,891],{"class":1289},[1258,4326,1293],{"class":1273},[1258,4328,2134],{"class":1269},[1258,4330,1314],{"class":1273},[1258,4332,4333],{"class":1260,"line":1933},[1258,4334,1336],{"emptyLinePlaceholder":1335},[1258,4336,4337],{"class":1260,"line":1938},[1258,4338,4339],{"class":1263},"    // 2. Create aperture (no config = log all events)\n",[1258,4341,4342,4345,4347,4349,4351,4353],{"class":1260,"line":1944},[1258,4343,4344],{"class":1269},"    cap",[1258,4346,1280],{"class":1269},[1258,4348,1657],{"class":1269},[1258,4350,1286],{"class":1273},[1258,4352,1861],{"class":1289},[1258,4354,1330],{"class":1273},[1258,4356,4357,4359,4361,4363,4365,4367,4369,4371,4373,4375,4377,4379,4381,4383,4385,4387],{"class":1260,"line":1971},[1258,4358,1839],{"class":1269},[1258,4360,1274],{"class":1273},[1258,4362,1844],{"class":1269},[1258,4364,1280],{"class":1269},[1258,4366,1283],{"class":1269},[1258,4368,1286],{"class":1273},[1258,4370,1290],{"class":1289},[1258,4372,1293],{"class":1273},[1258,4374,1296],{"class":1269},[1258,4376,1274],{"class":1273},[1258,4378,1301],{"class":1269},[1258,4380,1274],{"class":1273},[1258,4382,1306],{"class":1269},[1258,4384,1274],{"class":1273},[1258,4386,1311],{"class":1269},[1258,4388,1314],{"class":1273},[1258,4390,4391,4393,4395,4397,4399],{"class":1260,"line":1984},[1258,4392,1882],{"class":1319},[1258,4394,1844],{"class":1269},[1258,4396,1887],{"class":1319},[1258,4398,1890],{"class":1399},[1258,4400,1772],{"class":1273},[1258,4402,4403,4407,4409,4411],{"class":1260,"line":1999},[1258,4404,4406],{"class":4405},"skxcq","        panic",[1258,4408,1293],{"class":1273},[1258,4410,1908],{"class":1269},[1258,4412,1314],{"class":1273},[1258,4414,4415],{"class":1260,"line":2004},[1258,4416,1916],{"class":1273},[1258,4418,4419,4421,4423,4425,4427],{"class":1260,"line":2028},[1258,4420,1922],{"class":1319},[1258,4422,1323],{"class":1269},[1258,4424,1286],{"class":1273},[1258,4426,835],{"class":1289},[1258,4428,1330],{"class":1273},[1258,4430,4431],{"class":1260,"line":2041},[1258,4432,1336],{"emptyLinePlaceholder":1335},[1258,4434,4435],{"class":1260,"line":2056},[1258,4436,4437],{"class":1263},"    // 3. Emit events - they automatically become OTEL logs\n",[1258,4439,4440,4443,4445,4447,4449,4451,4453,4456,4458,4461],{"class":1260,"line":2061},[1258,4441,4442],{"class":1269},"    sig",[1258,4444,1280],{"class":1269},[1258,4446,1657],{"class":1269},[1258,4448,1286],{"class":1273},[1258,4450,1662],{"class":1289},[1258,4452,1293],{"class":1273},[1258,4454,4455],{"class":1418},"\"app.started\"",[1258,4457,1274],{"class":1273},[1258,4459,4460],{"class":1418}," \"Application started\"",[1258,4462,1314],{"class":1273},[1258,4464,4465,4467,4469,4471,4473,4475,4477,4480],{"class":1260,"line":2091},[1258,4466,4344],{"class":1269},[1258,4468,1286],{"class":1273},[1258,4470,963],{"class":1289},[1258,4472,1293],{"class":1273},[1258,4474,2134],{"class":1269},[1258,4476,1274],{"class":1273},[1258,4478,4479],{"class":1269}," sig",[1258,4481,1314],{"class":1273},[1258,4483,4484],{"class":1260,"line":2106},[1258,4485,1336],{"emptyLinePlaceholder":1335},[1258,4487,4488],{"class":1260,"line":2111},[1258,4489,4490],{"class":1263},"    // 4. Graceful shutdown\n",[1258,4492,4493,4495,4497,4499],{"class":1260,"line":2116},[1258,4494,4344],{"class":1269},[1258,4496,1286],{"class":1273},[1258,4498,891],{"class":1289},[1258,4500,1330],{"class":1273},[1258,4502,4503],{"class":1260,"line":2122},[1258,4504,2241],{"class":1273},[1244,4506,71],{"id":4507},"whats-happening",[2902,4509,4510,4516,4522,4528],{},[2403,4511,4512,4515],{},[2245,4513,4514],{},"OTEL providers"," - You configure how telemetry reaches your backend (exporters, batching, endpoints)",[2403,4517,4518,4521],{},[2245,4519,4520],{},"Aperture bridge"," - Registers as a capitan observer, receives all events",[2403,4523,4524,4527],{},[2245,4525,4526],{},"Automatic transformation"," - Events become OTEL logs with signal name and fields as attributes",[2403,4529,4530,4533],{},[2245,4531,4532],{},"Provider shutdown"," - Your responsibility to flush and close providers",[1164,4535,4536],{},"The key insight: aperture handles event-to-signal transformation, you handle OTEL configuration.",[1244,4538,76],{"id":4539},"with-configuration",[1164,4541,4542],{},"Add metrics and trace correlation:",[1249,4544,4546],{"className":1251,"code":4545,"language":1253,"meta":34,"style":34},"// Define signals\norderCreated := capitan.NewSignal(\"order.created\", \"Order created\")\norderCompleted := capitan.NewSignal(\"order.completed\", \"Order completed\")\norderID := capitan.NewStringKey(\"order_id\")\ntotal := capitan.NewFloat64Key(\"total\")\n\n// Create aperture\nap, _ := aperture.New(cap, logProvider, meterProvider, traceProvider)\ndefer ap.Close()\n\n// Configure transformations with schema\nschema := aperture.Schema{\n    // Count order creations\n    Metrics: []aperture.MetricSchema{\n        {\n            Signal: \"order.created\",\n            Name:   \"orders_created_total\",\n            Type:   \"counter\",\n        },\n    },\n\n    // Correlate order start/end into spans\n    Traces: []aperture.TraceSchema{\n        {\n            Start:          \"order.created\",\n            End:            \"order.completed\",\n            CorrelationKey: \"order_id\",\n            SpanName:       \"order_processing\",\n        },\n    },\n\n    // Only log order events\n    Logs: &aperture.LogSchema{\n        Whitelist: []string{\"order.created\", \"order.completed\"},\n    },\n}\nap.Apply(schema)\n\n// Emit order flow\ncap.Emit(ctx, orderCreated, orderID.Field(\"ORD-123\"), total.Field(99.99))\n// ^ Counter incremented, log recorded, span started\n\ntime.Sleep(100 * time.Millisecond)\n\ncap.Emit(ctx, orderCompleted, orderID.Field(\"ORD-123\"))\n// ^ Log recorded, span completed\n",[1255,4547,4548,4553,4577,4601,4620,4641,4645,4650,4684,4696,4700,4705,4720,4725,4745,4750,4763,4775,4787,4792,4797,4801,4806,4823,4827,4839,4851,4863,4875,4879,4883,4887,4892,4910,4935,4939,4943,4957,4961,4966,5009,5014,5018,5045,5049,5079],{"__ignoreMap":34},[1258,4549,4550],{"class":1260,"line":9},[1258,4551,4552],{"class":1263},"// Define signals\n",[1258,4554,4555,4558,4560,4562,4564,4566,4568,4570,4572,4575],{"class":1260,"line":19},[1258,4556,4557],{"class":1269},"orderCreated",[1258,4559,1280],{"class":1269},[1258,4561,1657],{"class":1269},[1258,4563,1286],{"class":1273},[1258,4565,1662],{"class":1289},[1258,4567,1293],{"class":1273},[1258,4569,1667],{"class":1418},[1258,4571,1274],{"class":1273},[1258,4573,4574],{"class":1418}," \"Order created\"",[1258,4576,1314],{"class":1273},[1258,4578,4579,4582,4584,4586,4588,4590,4592,4594,4596,4599],{"class":1260,"line":40},[1258,4580,4581],{"class":1269},"orderCompleted",[1258,4583,1280],{"class":1269},[1258,4585,1657],{"class":1269},[1258,4587,1286],{"class":1273},[1258,4589,1662],{"class":1289},[1258,4591,1293],{"class":1273},[1258,4593,1694],{"class":1418},[1258,4595,1274],{"class":1273},[1258,4597,4598],{"class":1418}," \"Order completed\"",[1258,4600,1314],{"class":1273},[1258,4602,4603,4606,4608,4610,4612,4614,4616,4618],{"class":1260,"line":812},[1258,4604,4605],{"class":1269},"orderID",[1258,4607,1280],{"class":1269},[1258,4609,1657],{"class":1269},[1258,4611,1286],{"class":1273},[1258,4613,1717],{"class":1289},[1258,4615,1293],{"class":1273},[1258,4617,1722],{"class":1418},[1258,4619,1314],{"class":1273},[1258,4621,4622,4625,4627,4629,4631,4634,4636,4639],{"class":1260,"line":1339},[1258,4623,4624],{"class":1269},"total",[1258,4626,1280],{"class":1269},[1258,4628,1657],{"class":1269},[1258,4630,1286],{"class":1273},[1258,4632,4633],{"class":1289},"NewFloat64Key",[1258,4635,1293],{"class":1273},[1258,4637,4638],{"class":1418},"\"total\"",[1258,4640,1314],{"class":1273},[1258,4642,4643],{"class":1260,"line":1345},[1258,4644,1336],{"emptyLinePlaceholder":1335},[1258,4646,4647],{"class":1260,"line":1370},[1258,4648,4649],{"class":1263},"// Create aperture\n",[1258,4651,4652,4654,4656,4658,4660,4662,4664,4666,4668,4670,4672,4674,4676,4678,4680,4682],{"class":1260,"line":1453},[1258,4653,1270],{"class":1269},[1258,4655,1274],{"class":1273},[1258,4657,1277],{"class":1269},[1258,4659,1280],{"class":1269},[1258,4661,1283],{"class":1269},[1258,4663,1286],{"class":1273},[1258,4665,1290],{"class":1289},[1258,4667,1293],{"class":1273},[1258,4669,1296],{"class":1269},[1258,4671,1274],{"class":1273},[1258,4673,1301],{"class":1269},[1258,4675,1274],{"class":1273},[1258,4677,1306],{"class":1269},[1258,4679,1274],{"class":1273},[1258,4681,1311],{"class":1269},[1258,4683,1314],{"class":1273},[1258,4685,4686,4688,4690,4692,4694],{"class":1260,"line":1465},[1258,4687,1320],{"class":1319},[1258,4689,1323],{"class":1269},[1258,4691,1286],{"class":1273},[1258,4693,835],{"class":1289},[1258,4695,1330],{"class":1273},[1258,4697,4698],{"class":1260,"line":1476},[1258,4699,1336],{"emptyLinePlaceholder":1335},[1258,4701,4702],{"class":1260,"line":1487},[1258,4703,4704],{"class":1263},"// Configure transformations with schema\n",[1258,4706,4707,4709,4711,4713,4715,4717],{"class":1260,"line":1498},[1258,4708,1348],{"class":1269},[1258,4710,1280],{"class":1269},[1258,4712,1283],{"class":1574},[1258,4714,1286],{"class":1273},[1258,4716,198],{"class":1574},[1258,4718,4719],{"class":1273},"{\n",[1258,4721,4722],{"class":1260,"line":1503},[1258,4723,4724],{"class":1263},"    // Count order creations\n",[1258,4726,4727,4731,4734,4737,4739,4741,4743],{"class":1260,"line":1511},[1258,4728,4730],{"class":4729},"sBGCq","    Metrics",[1258,4732,4733],{"class":1273},":",[1258,4735,4736],{"class":1273}," []",[1258,4738,1162],{"class":1574},[1258,4740,1286],{"class":1273},[1258,4742,203],{"class":1574},[1258,4744,4719],{"class":1273},[1258,4746,4747],{"class":1260,"line":1519},[1258,4748,4749],{"class":1273},"        {\n",[1258,4751,4752,4755,4757,4760],{"class":1260,"line":1527},[1258,4753,4754],{"class":4729},"            Signal",[1258,4756,4733],{"class":1273},[1258,4758,4759],{"class":1418}," \"order.created\"",[1258,4761,4762],{"class":1273},",\n",[1258,4764,4765,4768,4770,4773],{"class":1260,"line":1677},[1258,4766,4767],{"class":4729},"            Name",[1258,4769,4733],{"class":1273},[1258,4771,4772],{"class":1418},"   \"orders_created_total\"",[1258,4774,4762],{"class":1273},[1258,4776,4777,4780,4782,4785],{"class":1260,"line":1704},[1258,4778,4779],{"class":4729},"            Type",[1258,4781,4733],{"class":1273},[1258,4783,4784],{"class":1418},"   \"counter\"",[1258,4786,4762],{"class":1273},[1258,4788,4789],{"class":1260,"line":1727},[1258,4790,4791],{"class":1273},"        },\n",[1258,4793,4794],{"class":1260,"line":1750},[1258,4795,4796],{"class":1273},"    },\n",[1258,4798,4799],{"class":1260,"line":1755},[1258,4800,1336],{"emptyLinePlaceholder":1335},[1258,4802,4803],{"class":1260,"line":1760},[1258,4804,4805],{"class":1263},"    // Correlate order start/end into spans\n",[1258,4807,4808,4811,4813,4815,4817,4819,4821],{"class":1260,"line":1775},[1258,4809,4810],{"class":4729},"    Traces",[1258,4812,4733],{"class":1273},[1258,4814,4736],{"class":1273},[1258,4816,1162],{"class":1574},[1258,4818,1286],{"class":1273},[1258,4820,208],{"class":1574},[1258,4822,4719],{"class":1273},[1258,4824,4825],{"class":1260,"line":1793},[1258,4826,4749],{"class":1273},[1258,4828,4829,4832,4834,4837],{"class":1260,"line":1798},[1258,4830,4831],{"class":4729},"            Start",[1258,4833,4733],{"class":1273},[1258,4835,4836],{"class":1418},"          \"order.created\"",[1258,4838,4762],{"class":1273},[1258,4840,4841,4844,4846,4849],{"class":1260,"line":1804},[1258,4842,4843],{"class":4729},"            End",[1258,4845,4733],{"class":1273},[1258,4847,4848],{"class":1418},"            \"order.completed\"",[1258,4850,4762],{"class":1273},[1258,4852,4853,4856,4858,4861],{"class":1260,"line":1825},[1258,4854,4855],{"class":4729},"            CorrelationKey",[1258,4857,4733],{"class":1273},[1258,4859,4860],{"class":1418}," \"order_id\"",[1258,4862,4762],{"class":1273},[1258,4864,4865,4868,4870,4873],{"class":1260,"line":1830},[1258,4866,4867],{"class":4729},"            SpanName",[1258,4869,4733],{"class":1273},[1258,4871,4872],{"class":1418},"       \"order_processing\"",[1258,4874,4762],{"class":1273},[1258,4876,4877],{"class":1260,"line":1836},[1258,4878,4791],{"class":1273},[1258,4880,4881],{"class":1260,"line":1879},[1258,4882,4796],{"class":1273},[1258,4884,4885],{"class":1260,"line":1895},[1258,4886,1336],{"emptyLinePlaceholder":1335},[1258,4888,4889],{"class":1260,"line":1913},[1258,4890,4891],{"class":1263},"    // Only log order events\n",[1258,4893,4894,4897,4899,4902,4904,4906,4908],{"class":1260,"line":1919},[1258,4895,4896],{"class":4729},"    Logs",[1258,4898,4733],{"class":1273},[1258,4900,4901],{"class":1319}," &",[1258,4903,1162],{"class":1574},[1258,4905,1286],{"class":1273},[1258,4907,852],{"class":1574},[1258,4909,4719],{"class":1273},[1258,4911,4912,4915,4917,4919,4922,4925,4927,4929,4932],{"class":1260,"line":1933},[1258,4913,4914],{"class":4729},"        Whitelist",[1258,4916,4733],{"class":1273},[1258,4918,4736],{"class":1273},[1258,4920,4921],{"class":1574},"string",[1258,4923,4924],{"class":1273},"{",[1258,4926,1667],{"class":1418},[1258,4928,1274],{"class":1273},[1258,4930,4931],{"class":1418}," \"order.completed\"",[1258,4933,4934],{"class":1273},"},\n",[1258,4936,4937],{"class":1260,"line":1938},[1258,4938,4796],{"class":1273},[1258,4940,4941],{"class":1260,"line":1944},[1258,4942,2241],{"class":1273},[1258,4944,4945,4947,4949,4951,4953,4955],{"class":1260,"line":1971},[1258,4946,1270],{"class":1269},[1258,4948,1286],{"class":1273},[1258,4950,809],{"class":1289},[1258,4952,1293],{"class":1273},[1258,4954,1348],{"class":1269},[1258,4956,1314],{"class":1273},[1258,4958,4959],{"class":1260,"line":1984},[1258,4960,1336],{"emptyLinePlaceholder":1335},[1258,4962,4963],{"class":1260,"line":1999},[1258,4964,4965],{"class":1263},"// Emit order flow\n",[1258,4967,4968,4970,4972,4974,4976,4978,4980,4982,4984,4986,4988,4990,4992,4994,4996,4998,5000,5002,5004,5007],{"class":1260,"line":2004},[1258,4969,1296],{"class":1269},[1258,4971,1286],{"class":1273},[1258,4973,963],{"class":1289},[1258,4975,1293],{"class":1273},[1258,4977,2134],{"class":1269},[1258,4979,1274],{"class":1273},[1258,4981,2139],{"class":1269},[1258,4983,1274],{"class":1273},[1258,4985,2144],{"class":1269},[1258,4987,1286],{"class":1273},[1258,4989,2149],{"class":1289},[1258,4991,1293],{"class":1273},[1258,4993,2154],{"class":1418},[1258,4995,2190],{"class":1273},[1258,4997,2507],{"class":1269},[1258,4999,1286],{"class":1273},[1258,5001,2149],{"class":1289},[1258,5003,1293],{"class":1273},[1258,5005,5006],{"class":2202},"99.99",[1258,5008,2157],{"class":1273},[1258,5010,5011],{"class":1260,"line":2028},[1258,5012,5013],{"class":1263},"// ^ Counter incremented, log recorded, span started\n",[1258,5015,5016],{"class":1260,"line":2041},[1258,5017,1336],{"emptyLinePlaceholder":1335},[1258,5019,5020,5023,5025,5028,5030,5033,5036,5039,5041,5043],{"class":1260,"line":2056},[1258,5021,5022],{"class":1269},"time",[1258,5024,1286],{"class":1273},[1258,5026,5027],{"class":1289},"Sleep",[1258,5029,1293],{"class":1273},[1258,5031,5032],{"class":2202},"100",[1258,5034,5035],{"class":1269}," *",[1258,5037,5038],{"class":1269}," time",[1258,5040,1286],{"class":1273},[1258,5042,2211],{"class":1269},[1258,5044,1314],{"class":1273},[1258,5046,5047],{"class":1260,"line":2061},[1258,5048,1336],{"emptyLinePlaceholder":1335},[1258,5050,5051,5053,5055,5057,5059,5061,5063,5065,5067,5069,5071,5073,5075,5077],{"class":1260,"line":2091},[1258,5052,1296],{"class":1269},[1258,5054,1286],{"class":1273},[1258,5056,963],{"class":1289},[1258,5058,1293],{"class":1273},[1258,5060,2134],{"class":1269},[1258,5062,1274],{"class":1273},[1258,5064,2175],{"class":1269},[1258,5066,1274],{"class":1273},[1258,5068,2144],{"class":1269},[1258,5070,1286],{"class":1273},[1258,5072,2149],{"class":1289},[1258,5074,1293],{"class":1273},[1258,5076,2154],{"class":1418},[1258,5078,2157],{"class":1273},[1258,5080,5081],{"class":1260,"line":2106},[1258,5082,5083],{"class":1263},"// ^ Log recorded, span completed\n",[1244,5085,81],{"id":5086},"using-otel-directly",[1164,5088,5089],{},"Aperture exposes standard OTEL interfaces:",[1249,5091,5093],{"className":1251,"code":5092,"language":1253,"meta":34,"style":34},"// Get OTEL primitives\nlogger := ap.Logger(\"orders\")\nmeter := ap.Meter(\"orders\")\ntracer := ap.Tracer(\"orders\")\n\n// Create custom metrics\ncounter, _ := meter.Int64Counter(\"custom_counter\")\ncounter.Add(ctx, 1)\n\n// Create custom spans\nctx, span := tracer.Start(ctx, \"custom-operation\")\ndefer span.End()\n",[1255,5094,5095,5100,5120,5139,5158,5162,5167,5193,5213,5217,5222,5252],{"__ignoreMap":34},[1258,5096,5097],{"class":1260,"line":9},[1258,5098,5099],{"class":1263},"// Get OTEL primitives\n",[1258,5101,5102,5105,5107,5109,5111,5113,5115,5118],{"class":1260,"line":19},[1258,5103,5104],{"class":1269},"logger",[1258,5106,1280],{"class":1269},[1258,5108,1323],{"class":1269},[1258,5110,1286],{"class":1273},[1258,5112,820],{"class":1289},[1258,5114,1293],{"class":1273},[1258,5116,5117],{"class":1418},"\"orders\"",[1258,5119,1314],{"class":1273},[1258,5121,5122,5125,5127,5129,5131,5133,5135,5137],{"class":1260,"line":40},[1258,5123,5124],{"class":1269},"meter",[1258,5126,1280],{"class":1269},[1258,5128,1323],{"class":1269},[1258,5130,1286],{"class":1273},[1258,5132,825],{"class":1289},[1258,5134,1293],{"class":1273},[1258,5136,5117],{"class":1418},[1258,5138,1314],{"class":1273},[1258,5140,5141,5144,5146,5148,5150,5152,5154,5156],{"class":1260,"line":812},[1258,5142,5143],{"class":1269},"tracer",[1258,5145,1280],{"class":1269},[1258,5147,1323],{"class":1269},[1258,5149,1286],{"class":1273},[1258,5151,830],{"class":1289},[1258,5153,1293],{"class":1273},[1258,5155,5117],{"class":1418},[1258,5157,1314],{"class":1273},[1258,5159,5160],{"class":1260,"line":1339},[1258,5161,1336],{"emptyLinePlaceholder":1335},[1258,5163,5164],{"class":1260,"line":1345},[1258,5165,5166],{"class":1263},"// Create custom metrics\n",[1258,5168,5169,5172,5174,5176,5178,5181,5183,5186,5188,5191],{"class":1260,"line":1370},[1258,5170,5171],{"class":1269},"counter",[1258,5173,1274],{"class":1273},[1258,5175,1277],{"class":1269},[1258,5177,1280],{"class":1269},[1258,5179,5180],{"class":1269}," meter",[1258,5182,1286],{"class":1273},[1258,5184,5185],{"class":1289},"Int64Counter",[1258,5187,1293],{"class":1273},[1258,5189,5190],{"class":1418},"\"custom_counter\"",[1258,5192,1314],{"class":1273},[1258,5194,5195,5197,5199,5202,5204,5206,5208,5211],{"class":1260,"line":1453},[1258,5196,5171],{"class":1269},[1258,5198,1286],{"class":1273},[1258,5200,5201],{"class":1289},"Add",[1258,5203,1293],{"class":1273},[1258,5205,2134],{"class":1269},[1258,5207,1274],{"class":1273},[1258,5209,5210],{"class":2202}," 1",[1258,5212,1314],{"class":1273},[1258,5214,5215],{"class":1260,"line":1465},[1258,5216,1336],{"emptyLinePlaceholder":1335},[1258,5218,5219],{"class":1260,"line":1476},[1258,5220,5221],{"class":1263},"// Create custom spans\n",[1258,5223,5224,5226,5228,5231,5233,5236,5238,5241,5243,5245,5247,5250],{"class":1260,"line":1487},[1258,5225,2134],{"class":1269},[1258,5227,1274],{"class":1273},[1258,5229,5230],{"class":1269}," span",[1258,5232,1280],{"class":1269},[1258,5234,5235],{"class":1269}," tracer",[1258,5237,1286],{"class":1273},[1258,5239,5240],{"class":1289},"Start",[1258,5242,1293],{"class":1273},[1258,5244,2134],{"class":1269},[1258,5246,1274],{"class":1273},[1258,5248,5249],{"class":1418}," \"custom-operation\"",[1258,5251,1314],{"class":1273},[1258,5253,5254,5256,5258,5260,5263],{"class":1260,"line":1498},[1258,5255,1320],{"class":1319},[1258,5257,5230],{"class":1269},[1258,5259,1286],{"class":1273},[1258,5261,5262],{"class":1289},"End",[1258,5264,1330],{"class":1273},[1244,5266,86],{"id":5267},"next-steps",[2400,5269,5270,5276,5282,5288],{},[2403,5271,5272,5275],{},[1167,5273,91],{"href":5274},"concepts"," - Understand the core model",[2403,5277,5278,5281],{},[1167,5279,279],{"href":5280},"../guides/metrics"," - Configure metric transformations",[2403,5283,5284,5287],{},[1167,5285,342],{"href":5286},"../guides/traces"," - Set up trace correlation",[2403,5289,5290,5293],{},[1167,5291,630],{"href":5292},"../guides/testing"," - Test your aperture configuration",[2801,5295,5296],{},"html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}",{"title":34,"searchDepth":19,"depth":19,"links":5298},[5299,5300,5301,5302,5303,5304],{"id":1537,"depth":19,"text":61},{"id":3872,"depth":19,"text":66},{"id":4507,"depth":19,"text":71},{"id":4539,"depth":19,"text":76},{"id":5086,"depth":19,"text":81},{"id":5267,"depth":19,"text":86},{},"2025-12-11T00:00:00.000Z",null,{"title":53,"description":55},[1081,53],"2025-12-13T00:00:00.000Z","KOzMeKVq3luYlC0eMOeXDkyRYpuOX4fXtGxfVqrUoKg",[5313,5314],{"title":6,"path":5,"stem":1079,"description":8,"children":-1},{"title":91,"path":90,"stem":1088,"description":93,"children":-1},1776113596400]