Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.lasso.sh/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Lasso uses structured logging to provide comprehensive visibility into request routing, circuit breaker state transitions, and operational events. All logs follow a consistent JSON format for easy parsing and aggregation.

RequestContext Lifecycle

RequestContext is a stateless struct that threads through the entire request pipeline, capturing all routing decisions and timing data.

Context Structure

%RequestContext{
  # Request identification
  request_id: "d12fd341-cc14-fc97-ce9f-09876fffa7a3",
  chain: "ethereum",
  method: "eth_getLogs",
  transport: :http,
  strategy: :fastest,
  params_present: true,
  
  # Provider selection phase
  candidate_providers: [
    "ethereum_cloudflare:http",
    "ethereum_llamarpc:http",
    "ethereum_llamarpc:ws"
  ],
  selected_provider: %{id: "ethereum_llamarpc", protocol: :http},
  selection_reason: "fastest_method_latency",
  selection_start_time: 123456789,
  selection_end_time: 123456792,
  selection_latency_ms: 3,
  
  # Execution phase
  upstream_start_time: 123456792,
  upstream_end_time: 123457384,
  upstream_latency_ms: 592,
  retries: 0,
  circuit_breaker_state: :closed,
  
  # Result phase
  status: :success,
  result_type: "string",
  result_size_bytes: 11,
  end_to_end_latency_ms: 595,
  error: nil
}

Context Creation

Context created at the start of each request:
ctx = RequestContext.new(
  request_id: UUID.uuid4(),
  chain: "ethereum",
  method: "eth_blockNumber",
  transport: :http,
  strategy: :fastest,
  params_present: false
)

Context Updates

Context updated at each pipeline stage:
# 1. Selection phase
ctx = RequestContext.record_selection(
  ctx,
  candidate_providers,
  selected_provider,
  "fastest_method_latency"
)

# 2. Execution phase
ctx = RequestContext.mark_upstream_start(ctx)
ctx = RequestContext.record_circuit_state(ctx, :closed)
# ... execute request ...
ctx = RequestContext.mark_upstream_end(ctx)

# 3. Result phase
ctx = RequestContext.record_success(ctx, result)
# or
ctx = RequestContext.record_error(ctx, error)

Structured Log Schema

All completed requests emit a structured rpc.request.completed event.

Complete Event Example

{
  "event": "rpc.request.completed",
  "request_id": "d12fd341-cc14-fc97-ce9f-09876fffa7a3",
  "strategy": "fastest",
  "chain": "ethereum",
  "transport": "http",
  "jsonrpc_method": "eth_getLogs",
  "params_present": true,
  "routing": {
    "candidate_providers": [
      "ethereum_cloudflare:http",
      "ethereum_llamarpc:http",
      "ethereum_llamarpc:ws"
    ],
    "selected_provider": {
      "id": "ethereum_llamarpc",
      "protocol": "http"
    },
    "selection_reason": "fastest_method_latency",
    "retries": 2,
    "circuit_breaker_state": "closed"
  },
  "timing": {
    "selection_latency_ms": 3,
    "upstream_latency_ms": 592,
    "end_to_end_latency_ms": 595
  },
  "response": {
    "status": "success",
    "result_type": "array",
    "result_size_bytes": 4521
  }
}

Error Event Example

{
  "event": "rpc.request.completed",
  "request_id": "abc-123",
  "strategy": "load_balanced",
  "chain": "ethereum",
  "transport": "http",
  "jsonrpc_method": "eth_call",
  "routing": {
    "selected_provider": {
      "id": "ethereum_cloudflare",
      "protocol": "http"
    },
    "retries": 0,
    "circuit_breaker_state": "closed"
  },
  "timing": {
    "selection_latency_ms": 2,
    "upstream_latency_ms": 125,
    "end_to_end_latency_ms": 127
  },
  "response": {
    "status": "error",
    "error": {
      "code": -32000,
      "message": "Cannot fulfill request",
      "category": "server_error"
    }
  }
}

Field Descriptions

Routing section:
FieldTypeDescription
candidate_providersarrayProviders considered (format: "provider_id:protocol")
selected_providerobjectProvider chosen for execution
selection_reasonstringWhy this provider was selected
retriesintegerNumber of retry attempts (0 = first try succeeded)
circuit_breaker_statestringCB state when request was made
Selection reasons:
  • "fastest_method_latency" - Lowest latency for this method (fastest strategy)
  • "static_priority" - Config priority (priority strategy)
  • "load_balanced_rotation" - Load balancing (load_balanced strategy)
  • "latency_weighted_selection" - Weighted random (latency_weighted strategy)
Timing section:
FieldTypeDescription
selection_latency_msintegerProvider selection duration
upstream_latency_msintegerTime from sending request to receiving response
end_to_end_latency_msintegerTotal request duration
Response section:
FieldTypeDescription
statusstring"success" or "error"
result_typestringType of result (success only)
result_size_bytesintegerByte size of result (success only)
errorobjectError details (error only)

TelemetryLogger

TelemetryLogger bridges telemetry events to structured logs for production debugging.

Attached Handlers

Lasso.TelemetryLogger.attach()
Attaches handlers for:
EventLog LevelDescription
[:lasso, :failover, :fast_fail]WARNINGProvider failover triggered
[:lasso, :failover, :circuit_open]WARNINGCircuit breaker blocked request
[:lasso, :failover, :degraded_mode]WARNINGEntered degraded mode
[:lasso, :failover, :degraded_success]INFORecovered via degraded mode
[:lasso, :failover, :exhaustion]ERRORAll providers exhausted
[:lasso, :request, :slow]WARNINGRequest took >2000ms
[:lasso, :request, :very_slow]ERRORRequest took >4000ms
[:lasso, :circuit_breaker, :open]WARNINGCircuit breaker opened
[:lasso, :circuit_breaker, :close]INFOCircuit breaker closed
[:lasso, :circuit_breaker, :half_open]INFOCircuit breaker half-open
[:lasso, :circuit_breaker, :proactive_recovery]INFOProactive recovery attempt

Example Log Outputs

Failover event:
[warning] Failover: eth_getLogs ethereum_cloudflare:http -> rate_limit
  chain: ethereum
  request_id: d12fd341-cc14-fc97-ce9f-09876fffa7a3
Circuit breaker opened:
[warning] Circuit opened: ethereum_llamarpc:http
  instance_id: ethereum_llamarpc
  from: closed
  to: open
  reason: failure_threshold_exceeded
  failure_count: 5
  error_category: network_error
Slow request:
[warning] Slow (>2s): eth_getLogs ethereum_alchemy:http 2350ms
  chain: ethereum
All providers exhausted:
[error] Exhausted: eth_call all providers failed (retry_after: 5000ms)
  chain: ethereum

Configuration

Enable/disable specific log categories:
# config/config.exs
config :lasso, Lasso.TelemetryLogger,
  enabled: true,
  log_slow_requests: true,
  log_failovers: true,
  log_circuit_breaker: true
Disable all telemetry logging:
config :lasso, Lasso.TelemetryLogger,
  enabled: false

Telemetry Event Schemas

Circuit Breaker Events

All circuit breaker events use consistent metadata:
:telemetry.execute(
  [:lasso, :circuit_breaker, :open],
  %{count: 1},
  %{
    instance_id: "ethereum_llamarpc",
    transport: :http,
    from_state: :closed,
    to_state: :open,
    reason: :failure_threshold_exceeded,
    failure_count: 5,
    error_category: :network_error
  }
)
State transition reasons:
ReasonDescription
:failure_threshold_exceededOpened due to consecutive failures
:reopen_due_to_failureRe-opened from half_open after failure
:recoveredClosed after successful recovery
:attempt_recoveryTransitioning to half_open to test recovery
:proactive_recoveryTimer-based recovery attempt
:manual_open / :manual_closeManual intervention

Failover Events

:telemetry.execute(
  [:lasso, :failover, :fast_fail],
  %{count: 1},
  %{
    chain: "ethereum",
    provider_id: "ethereum_cloudflare",
    transport: :http,
    error_category: :rate_limit,
    method: "eth_getLogs",
    request_id: "d12fd341-cc14-fc97-ce9f-09876fffa7a3"
  }
)

Request Timing Events

:telemetry.execute(
  [:lasso, :request, :slow],
  %{latency_ms: 2350},
  %{
    chain: "ethereum",
    method: "eth_getLogs",
    provider: "ethereum_alchemy",
    transport: :http
  }
)

Privacy & Redaction

Error Message Truncation

Error messages limited to prevent logging unbounded responses:
@max_error_message_chars 256

def truncate_error_message(message) do
  if String.length(message) > @max_error_message_chars do
    String.slice(message, 0, @max_error_message_chars) <> "..."
  else
    message
  end
end
Example:
{
  "error": {
    "code": -32000,
    "message": "Request failed: execution reverted: ERC20: transfer amount exceeds balance. Contract address: 0x1234567890abcdef... (truncated)",
    "category": "server_error"
  }
}

No Secrets in Logs

  • Provider URLs: Not logged (only provider IDs)
  • API Keys: Never exposed
  • Client IPs: Not logged by default
  • Request parameters: Only presence flag (params_present: true/false)

Sampling

Reduce log volume in high-traffic scenarios:
# config/config.exs
config :lasso, :observability,
  sampling: [rate: 0.1]  # Log 10% of requests
Sampling behavior:
  • Random sampling: Each request independently sampled
  • Client metadata unaffected: Sampling only affects server logs
  • Rate: Float between 0.0 (none) and 1.0 (all)
Environment-specific sampling:
# config/dev.exs
config :lasso, :observability,
  sampling: [rate: 1.0]  # Log all requests in dev

# config/prod.exs
config :lasso, :observability,
  sampling: [rate: 0.5]  # 50% sampling in production

# config/test.exs
config :lasso, :observability,
  sampling: [rate: 0.0]  # Disable in tests

Configuration Reference

Complete Configuration

# config/config.exs
config :lasso, :observability,
  # Log level for rpc.request.completed events
  log_level: :info,
  
  # Maximum error message length in logs
  max_error_message_chars: 256,
  
  # Maximum size for X-Lasso-Meta header
  max_meta_header_bytes: 4096,
  
  # Sampling configuration
  sampling: [
    rate: 1.0  # 1.0 = log all requests, 0.1 = log 10%
  ]

Log Parsing & Analysis

Extract Timing Data

cat logs/app.log | grep 'rpc.request.completed' | jq '.timing'
Output:
{"selection_latency_ms": 3, "upstream_latency_ms": 592, "end_to_end_latency_ms": 595}
{"selection_latency_ms": 2, "upstream_latency_ms": 145, "end_to_end_latency_ms": 147}

Filter by Provider

cat logs/app.log | grep 'rpc.request.completed' | jq 'select(.routing.selected_provider.id == "ethereum_llamarpc")'

Calculate Success Rate

cat logs/app.log | grep 'rpc.request.completed' | jq -s '
  (map(select(.response.status == "success")) | length) / length * 100
'

Identify Slow Methods

cat logs/app.log | grep 'rpc.request.completed' | jq -s '
  group_by(.jsonrpc_method) | 
  map({
    method: .[0].jsonrpc_method,
    avg_latency: (map(.timing.upstream_latency_ms) | add / length),
    count: length
  }) | 
  sort_by(.avg_latency) | 
  reverse
'

Performance Characteristics

Overhead Breakdown

OperationOverheadNotes
Context creation<1msSingle struct allocation
Timing markers<0.1msSystem.monotonic_time/0
Log emission<5msAsync logger, sampling
Total (without metadata)~8msEnd-to-end overhead

Memory Usage

  • RequestContext struct: ~200 bytes per request
  • Process dictionary storage: ~200 bytes (until response sent)
  • Peak usage: <1KB per in-flight request

Summary

Lasso’s logging system provides:
  • Structured JSON logs for easy parsing and aggregation
  • RequestContext lifecycle tracking all routing decisions
  • TelemetryLogger for operational events (failovers, circuit breakers)
  • Privacy-first with error truncation and no secrets in logs
  • Sampling support for high-traffic scenarios
  • Low overhead (<8ms per request, <1KB per in-flight request)
  • Telemetry integration for custom monitoring