Logs Guide
Transform capitan events into OTEL logs.
Default Behavior
With no log configuration, all events are logged:
// No log config = log everything
ap, _ := aperture.New(cap, logProvider, meterProvider, traceProvider)
schema := aperture.Schema{
Metrics: []aperture.MetricSchema{...},
// Logs: nil - all events logged
}
ap.Apply(schema)
sig1 := capitan.NewSignal("order.created", "Order created")
sig2 := capitan.NewSignal("order.shipped", "Order shipped")
cap.Emit(ctx, sig1) // Logged
cap.Emit(ctx, sig2) // Logged
Whitelist Filtering
Log only specific signals:
orderCreated := capitan.NewSignal("order.created", "Order created")
orderFailed := capitan.NewSignal("order.failed", "Order failed")
orderShipped := capitan.NewSignal("order.shipped", "Order shipped")
schema := aperture.Schema{
Logs: &aperture.LogSchema{
Whitelist: []string{
"order.created",
"order.failed",
},
},
}
cap.Emit(ctx, orderCreated) // Logged
cap.Emit(ctx, orderFailed) // Logged
cap.Emit(ctx, orderShipped) // NOT logged (not in whitelist)
Log Attributes
Event fields become log attributes:
orderCreated := capitan.NewSignal("order.created", "Order created")
orderID := capitan.NewStringKey("order_id")
total := capitan.NewFloat64Key("total")
items := capitan.NewIntKey("items")
cap.Emit(ctx, orderCreated,
orderID.Field("ORD-123"),
total.Field(99.99),
items.Field(3),
)
Produces log record with attributes:
signal="order.created"
order_id="ORD-123"
total=99.99
items=3
Signal Metadata
Every log record includes standard attributes:
| Attribute | Source | Description |
|---|---|---|
capitan.signal | Event.Signal() | Signal name |
capitan.signal.description | Signal description | Signal description |
| Timestamp | Event.Timestamp() | Event timestamp |
| Severity | Event.Severity() | Capitan severity level |
Severity Mapping
Capitan severity maps to OTEL log severity:
| Capitan | OTEL |
|---|---|
Debug | DEBUG |
Info | INFO |
Warn | WARN |
Error | ERROR |
Fatal | FATAL |
Context Extraction for Logs
Add context values as log attributes:
type ctxKey string
const (
userIDKey ctxKey = "user_id"
requestKey ctxKey = "request_id"
)
ap.RegisterContextKey("user_id", userIDKey)
ap.RegisterContextKey("request_id", requestKey)
schema := aperture.Schema{
Context: &aperture.ContextSchema{
Logs: []string{"user_id", "request_id"},
},
}
ap.Apply(schema)
ctx = context.WithValue(ctx, userIDKey, "user-123")
ctx = context.WithValue(ctx, requestKey, "req-456")
cap.Emit(ctx, sig)
// Log includes: user_id="user-123", request_id="req-456"
Stdout Logging
Enable console logging alongside OTEL:
schema := aperture.Schema{
Stdout: true,
}
cap.Emit(ctx, sig)
// 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
Custom Type Handling
Custom types are automatically JSON serialized:
type OrderInfo struct {
ID string `json:"id"`
Total float64 `json:"total"`
Secret string `json:"-"` // Excluded from JSON
}
orderKey := capitan.NewKey[OrderInfo]("order", "Order details")
cap.Emit(ctx, sig, orderKey.Field(OrderInfo{
ID: "ORD-123",
Total: 99.99,
Secret: "xxx",
}))
// Log includes: order="{\"id\":\"ORD-123\",\"total\":99.99}"
// Secret excluded via json:"-" tag
Use JSON struct tags to control what gets exported.
Using Logger Directly
Access the underlying OTEL logger:
logger := ap.Logger("orders")
// Direct log emission
var record log.Record
record.SetBody(log.StringValue("Direct log message"))
record.SetSeverity(log.SeverityInfo)
record.AddAttributes(log.String("order_id", "ORD-123"))
logger.Emit(ctx, record)
Built-in Field Types
All capitan field types are transformed:
| Capitan Type | Log Attribute |
|---|---|
string | log.String(key, value) |
int, int32, int64 | log.Int64(key, value) |
uint, uint32, uint64 | log.Int64(key, value) |
float32, float64 | log.Float64(key, value) |
bool | log.Bool(key, value) |
time.Time | log.String(key, rfc3339) |
time.Duration | log.Int64(key, millis) |
[]byte | log.Bytes(key, value) |
| Custom types | log.String(key, json) |
Schema Configuration
Via YAML:
logs:
whitelist:
- order.created
- order.failed
- payment.failed
stdout: true
Load and apply:
configBytes, _ := os.ReadFile("config.yaml")
schema, _ := aperture.LoadSchemaFromYAML(configBytes)
ap.Apply(schema)
Performance Considerations
- 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