Pular para o conteúdo principal
ImmutableLog logo
VoltarRuby

Ruby

Guia completo de integração com o ImmutableLog em Ruby. Middleware automático para Rails (Rack) e Sinatra — ou um cliente HTTP direto com Net::HTTP para workers Sidekiq, rake tasks e scripts. O envio roda em uma Thread fire-and-forget.

Cliente HTTP direto (Net::HTTP)

Use a classe ImmutableLog para enviar eventos diretamente sem depender de um framework web. Ideal para workers Sidekiq, rake tasks e qualquer script Ruby que precise registrar eventos. Sem dependências além da stdlib.

ruby
# lib/immutable_log.rb
require "net/http"
require "json"
require "securerandom"
require "time"
require "uri"

class ImmutableLog
  def initialize(api_key:, service: "ruby-service", env: "production", api_url: nil)
    @api_key = api_key
    @service = service
    @env = env
    @api_url = api_url || ENV.fetch("IMTBL_URL", "https://api.immutablelog.com")
  end

  def send_event(event_name:, kind:, payload:, immutable_trail: nil)
    request_id = SecureRandom.uuid
    trail = self.class.sanitize_trail(immutable_trail)

    # Todos os valores de meta precisam ser strings.
    meta = {
      "type" => kind,
      "event_name" => event_name,
      "service" => @service,
      "request_id" => request_id,
      "env" => @env,
    }
    meta["immutable_trail"] = trail if trail

    body = {
      "payload" => JSON.generate(payload.merge("timestamp" => Time.now.utc.iso8601)),
      "meta" => meta,
    }

    uri = URI.join(@api_url, "/v1/events")
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme == "https"
    http.open_timeout = 5
    http.read_timeout = 5

    req = Net::HTTP::Post.new(uri)
    req["Authorization"] = "Bearer #{@api_key}"
    req["Content-Type"] = "application/json"
    # Idempotency-Key e OBRIGATORIO (sem ele a API responde 400).
    req["Idempotency-Key"] = "#{event_name}-#{request_id}"
    req["Request-Id"] = request_id
    req["X-Client-TZ"] = "America/Sao_Paulo"
    req["X-Client-Offset-Minutes"] = "-180"
    req["X-Imtbl-Trail"] = trail if trail
    req.body = JSON.generate(body)

    http.request(req)
  end

  # Normaliza o immutable_trail (trim, nao-vazio, max 256, sem ':').
  # O servidor responde 400 invalid_immutable_trail se a regra for violada.
  def self.sanitize_trail(value)
    return nil unless value.is_a?(String)
    v = value.strip
    return nil if v.empty?
    v = v.tr(":", "-")
    v = v[0, 256] if v.length > 256
    v
  end
end

# Uso (fire-and-forget via Thread):
log = ImmutableLog.new(api_key: ENV["IMTBL_API_KEY"], service: "payments-service")
Thread.new do
  log.send_event(
    event_name: "payment.approved", kind: "success",
    payload: { "payment_id" => "pay_abc123", "amount" => 299.90 },
    immutable_trail: "order-7782",
  )
end

Para não bloquear o request, envolva o send_event em uma Thread (fire-and-forget) ou despache para um worker Sidekiq/ActiveJob.

Retry com backoff exponencial

Para produção de alta disponibilidade, adicione retry com backoff exponencial. O ImmutableLog retorna 202 em sucesso e 200 em duplicata idempotente — nunca faça retry em 429 (limite mensal atingido).

ruby
def send_with_retry(event_name:, kind:, payload:, immutable_trail: nil, max_attempts: 3)
  delay = 0.5

  max_attempts.times do |attempt|
    res = send_event(event_name: event_name, kind: kind,
                     payload: payload, immutable_trail: immutable_trail)
    code = res.code.to_i
    return res if code < 400

    # 429 = limite mensal — nao fazer retry.
    return res if code == 429

    if attempt + 1 < max_attempts
      sleep(delay)
      delay *= 2 # 0.5s -> 1s -> 2s
    end
  end
  nil
rescue StandardError
  nil
end

Hash de dados sensíveis

Nunca envie dados pessoais brutos (e-mail, CPF, IP) para o ImmutableLog. Use SHA-256 para gerar um digest determinístico — rastreável sem expor o dado original.

ruby
require "digest"

# Faz o hash de dados sensiveis antes de enviar — nunca logue PII bruto.
def sha256_hex(data)
  Digest::SHA256.hexdigest(data)
end

# Uso no payload do evento:
payload = {
  "user_id"    => "usr_123",
  "email_hash" => sha256_hex("user@example.com"),
  "ip_hash"    => sha256_hex("192.168.1.1"),
}

Esta documentação reflete o comportamento atual da API. Para dúvidas ou integrações avançadas, entre em contato com o time de suporte.