Skip to main content
ImmutableLog logo
BackGo / Golang

Go

Complete integration guide for ImmutableLog in Go. Automatic middleware for net/http, Gin, and Fiber — or direct HTTP client for workers, Kafka consumers, and scheduled jobs. Fire-and-forget goroutines guarantee zero overhead on response latency.

Middlewares

Automatic integration via middleware — every HTTP request is captured without modifying any handler. Click the framework to see the complete code.

Direct HTTP client

Use the Client to send events directly to ImmutableLog without depending on a web framework. Ideal for workers, Kafka consumers, cron jobs, and any Go code that needs to record events.

go
package immutablelog

import (
	"bytes"
	"context"
	"crypto/sha256"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"time"
)

type Client struct {
	APIKey      string
	ServiceName string
	Env         string
	APIURL      string
	httpClient  *http.Client
}

func NewClient(apiKey, serviceName, env string) *Client {
	return &Client{
		APIKey:      apiKey,
		ServiceName: serviceName,
		Env:         env,
		APIURL:      "https://api.immutablelog.com",
		httpClient:  &http.Client{Timeout: 5 * time.Second},
	}
}

type SendOpts struct {
	EventName string
	Type      string // "success" | "info" | "error"
	Payload   any    // will be marshaled to JSON string
}

// Send sends a single event. Returns immediately — use SendAsync for fire-and-forget.
func (c *Client) Send(ctx context.Context, opts SendOpts) error {
	payloadJSON, err := json.Marshal(opts.Payload)
	if err != nil {
		return fmt.Errorf("marshal payload: %w", err)
	}

	requestID := fmt.Sprintf("%d", time.Now().UnixNano())

	body := map[string]any{
		"payload": string(payloadJSON),
		"meta": map[string]string{
			"type":       opts.Type,
			"event_name": opts.EventName,
			"service":    c.ServiceName,
			"env":        c.Env,
			"request_id": requestID,
		},
	}

	bodyJSON, err := json.Marshal(body)
	if err != nil {
		return fmt.Errorf("marshal body: %w", err)
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodPost,
		c.APIURL+"/v1/events", bytes.NewBuffer(bodyJSON))
	if err != nil {
		return fmt.Errorf("create request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+c.APIKey)
	req.Header.Set("Idempotency-Key", requestID)

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("send event: %w", err)
	}
	defer resp.Body.Close()
	return nil
}

// SendAsync sends the event in a goroutine. Never blocks the caller.
func (c *Client) SendAsync(opts SendOpts) {
	go func() {
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		_ = c.Send(ctx, opts)
	}()
}

// Usage:
// client := immutablelog.NewClient(os.Getenv("IMTBL_API_KEY"), "my-service", "production")
//
// client.SendAsync(immutablelog.SendOpts{
//     EventName: "user.created",
//     Type:      "success",
//     Payload:   map[string]any{"user_id": "usr_123", "plan": "pro"},
// })

SendAsync() fires a goroutine and returns immediately. Use Send() when you need the return error. Both respect context.Context for cancellation and timeout.

Retry with exponential backoff

For high-availability production, add retry with exponential backoff. ImmutableLog returns 202 on success — never retry on 429 (monthly limit reached).

go
// SendWithRetry retries on transient errors (not on 429).
func (c *Client) SendWithRetry(ctx context.Context, opts SendOpts, maxAttempts int) error {
	delay := 500 * time.Millisecond

	for attempt := 0; attempt < maxAttempts; attempt++ {
		err := c.Send(ctx, opts)
		if err == nil {
			return nil
		}

		// Do not retry on context cancellation
		if ctx.Err() != nil {
			return ctx.Err()
		}

		if attempt+1 < maxAttempts {
			time.Sleep(delay)
			delay *= 2 // exponential backoff: 500ms → 1s → 2s
		}
	}

	return fmt.Errorf("failed after %d attempts", maxAttempts)
}

Batch sending

In high-frequency workers, send multiple events in parallel with sync.WaitGroup. Each event is sent individually — the WaitGroup waits for all to complete.

go
// SendBatch sends multiple events concurrently using goroutines.
// Uses a WaitGroup to wait for all sends to complete.
func (c *Client) SendBatch(ctx context.Context, events []SendOpts) []error {
	errs := make([]error, len(events))
	var wg sync.WaitGroup

	for i, evt := range events {
		wg.Add(1)
		go func(idx int, e SendOpts) {
			defer wg.Done()
			errs[idx] = c.Send(ctx, e)
		}(i, evt)
	}

	wg.Wait()
	return errs
}

// Usage:
ctx := context.Background()
errs := client.SendBatch(ctx, []immutablelog.SendOpts{
	{EventName: "order.created", Type: "success", Payload: map[string]any{"order_id": "ord_1"}},
	{EventName: "payment.captured", Type: "success", Payload: map[string]any{"amount": 9900}},
	{EventName: "email.sent", Type: "info", Payload: map[string]any{"template": "welcome"}},
})

Sensitive data hashing

Never send raw personal data (email, CPF, IP) to ImmutableLog. Use SHA-256 to generate a deterministic digest — traceable without exposing the original data.

go
import (
	"crypto/sha256"
	"fmt"
)

// Hash sensitive data before sending — never log raw PII.
func sha256Hex(data []byte) string {
	h := sha256.Sum256(data)
	return fmt.Sprintf("%x", h)
}

// Usage in event payload:
payload := map[string]any{
	"user_id":    "usr_123",
	"email_hash": sha256Hex([]byte("user@example.com")),
	"ip_hash":    sha256Hex([]byte("192.168.1.1")),
}

This documentation reflects the current API behavior. For questions or advanced integrations, contact the support team.