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.
Middleware for the standard library. Zero dependencies — compatible with gorilla/mux, chi, and any http.Handler router.
func Middleware(opts Options) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
startedAt := time.Now()
wrapped := &responseWriter{ResponseWriter: w}
next.ServeHTTP(wrapped, r)
go emit(r, wrapped.Status(), time.Since(startedAt), opts)
})
}
}View full documentation →
gin.HandlerFunc with c.Next(), c.Writer.Status(), and c.Set() for custom events. Supports route groups and auth abort.
func Middleware(cfg Config) gin.HandlerFunc {
return func(c *gin.Context) {
startedAt := time.Now()
c.Next()
elapsed := time.Since(startedAt)
status := c.Writer.Status()
eventName, _ := c.Get("imtbl.eventName")
go emit(c.Request, status, elapsed, eventName, cfg)
}
}View full documentation →
fiber.Handler with fasthttp. Copies the body before c.Next() to avoid buffer recycling. Captures Go errors returned by the chain.
func New(cfg Config) fiber.Handler {
return func(c *fiber.Ctx) error {
body := make([]byte, len(c.Body()))
copy(body, c.Body())
startedAt := time.Now()
err := c.Next()
status := c.Response().StatusCode()
go emit(c, status, time.Since(startedAt), body, err, cfg)
return err
}
}View full documentation →
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.
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).
// 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.
// 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.
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.
