LogTide

Official Go SDK for LogTide with automatic batching, retry logic, circuit breaker pattern, and native OpenTelemetry integration.

Installation

go get github.com/logtide-dev/logtide-sdk-go

Quick Start

package main

import (
  "context"
  "github.com/logtide-dev/logtide-sdk-go"
)

func main() {
  client, err := logtide.New(
      logtide.WithAPIKey("lp_your_api_key"),
      logtide.WithService("my-service"),
  )
  if err != nil {
      panic(err)
  }
  defer client.Close()

  ctx := context.Background()

  // Send logs
  client.Info(ctx, "Server started", map[string]any{"port": 8080})
  client.Error(ctx, "Connection failed", map[string]any{"error": "timeout"})
}

Features

  • ✅ Automatic batching with configurable size and interval
  • ✅ Retry logic with exponential backoff
  • ✅ Circuit breaker pattern for fault tolerance
  • ✅ Goroutine-safe logging
  • ✅ Context support for request-scoped logging
  • ✅ Native OpenTelemetry integration
  • ✅ Graceful shutdown with flush
  • ✅ Structured metadata support
  • ✅ 87% test coverage

Configuration

client, err := logtide.New(
  // Required
  logtide.WithAPIKey("lp_your_api_key"),
  logtide.WithService("my-service"),

  // Optional - API
  logtide.WithBaseURL("https://api.logtide.dev"),

  // Optional - Performance
  logtide.WithBatchSize(100),              // Max logs per batch (default: 100)
  logtide.WithFlushInterval(5*time.Second), // Flush interval (default: 5s)
  logtide.WithTimeout(30*time.Second),      // HTTP timeout (default: 30s)

  // Optional - Reliability
  logtide.WithRetry(3, 1*time.Second, 60*time.Second), // maxRetries, delay, maxDelay
  logtide.WithCircuitBreaker(5, 30*time.Second),       // threshold, timeout

  // Optional - Metadata
  logtide.WithGlobalMetadata(map[string]any{
      "environment": "production",
      "version":     "1.0.0",
  }),
)

Logging Methods

Basic Logging

ctx := context.Background()

// Log levels: Debug, Info, Warn, Error, Critical
client.Debug(ctx, "Debug message", map[string]any{"detail": "value"})
client.Info(ctx, "Request received", map[string]any{"method": "GET", "path": "/users"})
client.Warn(ctx, "Cache miss", map[string]any{"key": "user:123"})
client.Error(ctx, "Query failed", map[string]any{"query": "SELECT *"})
client.Critical(ctx, "Out of memory", map[string]any{"used": "95%"})

With Trace ID

// Add trace ID to context
ctx := logtide.WithTraceID(context.Background(), "550e8400-e29b-41d4-a716-446655440000")

// All logs will include the trace ID
client.Info(ctx, "Processing request", nil)
client.Info(ctx, "Query executed", nil)

Error Handling

err := client.Info(ctx, "message", nil)
if err != nil {
  switch {
  case errors.Is(err, logtide.ErrClientClosed):
      // Client was closed, logs won't be sent
  case errors.Is(err, logtide.ErrCircuitOpen):
      // Circuit breaker is open, too many failures
  case errors.Is(err, logtide.ErrInvalidAPIKey):
      // Invalid API key configuration
  default:
      // Other error
      log.Printf("Failed to send log: %v", err)
  }
}

OpenTelemetry Integration

import (
  "go.opentelemetry.io/otel"
)

tracer := otel.Tracer("my-service")

func handleRequest(ctx context.Context) {
  ctx, span := tracer.Start(ctx, "handle-request")
  defer span.End()

  // Trace ID and Span ID are automatically extracted from context
  client.Info(ctx, "Processing request", map[string]any{
      "user_id": 123,
  })

  // Nested spans work too
  ctx, dbSpan := tracer.Start(ctx, "database-query")
  client.Debug(ctx, "Executing query", nil)
  dbSpan.End()
}

HTTP Middleware

Standard Library

func LoggingMiddleware(client *logtide.Client) func(http.Handler) http.Handler {
  return func(next http.Handler) http.Handler {
      return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
          start := time.Now()

          // Create wrapped response writer to capture status
          wrapped := &responseWriter{ResponseWriter: w, statusCode: 200}

          next.ServeHTTP(wrapped, r)

          client.Info(r.Context(), "HTTP request", map[string]any{
              "method":      r.Method,
              "path":        r.URL.Path,
              "status":      wrapped.statusCode,
              "duration_ms": time.Since(start).Milliseconds(),
          })
      })
  }
}

// Usage
mux := http.NewServeMux()
handler := LoggingMiddleware(client)(mux)
http.ListenAndServe(":8080", handler)

Gin Framework

func GinLoggingMiddleware(client *logtide.Client) gin.HandlerFunc {
  return func(c *gin.Context) {
      start := time.Now()

      c.Next()

      client.Info(c.Request.Context(), "HTTP request", map[string]any{
          "method":      c.Request.Method,
          "path":        c.Request.URL.Path,
          "status":      c.Writer.Status(),
          "duration_ms": time.Since(start).Milliseconds(),
      })
  }
}

// Usage
r := gin.Default()
r.Use(GinLoggingMiddleware(client))

Best Practices

1. Always Defer Close
Use defer client.Close() immediately after creating the client to ensure all buffered logs are flushed on shutdown.
2. Pass Context
Always pass the request context to logging methods. This enables trace correlation and allows logs to be cancelled with the request.
3. Use Global Metadata
Add environment, version, and hostname as global metadata instead of repeating them in every log call.
4. Handle Errors Appropriately
Check for specific errors like ErrCircuitOpen to implement fallback logging strategies when LogTide is unavailable.