Official Go SDK for Logwell - a self-hosted logging platform with real-time streaming and full-text search.
- Zero external dependencies - Uses only Go standard library
- Automatic batching - Configurable batch size and flush intervals
- Retry with exponential backoff - Automatic retry on transient failures
- Child loggers - Request-scoped context propagation
- Source location capture - Opt-in file/line number tracking
- Thread-safe - Safe for concurrent use from multiple goroutines
- Context support - Flush and Shutdown respect context cancellation
go get github.com/Divkix/Logwell/sdks/gopackage main
import (
"context"
"log"
"github.com/Divkix/Logwell/sdks/go/logwell"
)
func main() {
// Create a new client
client, err := logwell.New(
"https://logs.example.com",
"lw_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
logwell.WithService("my-app"),
)
if err != nil {
log.Fatal(err)
}
// Log at different levels
client.Debug("Debug message")
client.Info("User logged in", logwell.M{"userId": "123"})
client.Warn("Deprecated API called")
client.Error("Database connection failed", logwell.M{"host": "db.local"})
client.Fatal("Unrecoverable error")
// Flush before shutdown
if err := client.Shutdown(context.Background()); err != nil {
log.Printf("Shutdown error: %v", err)
}
}Configure the client using functional options:
| Option | Type | Default | Description |
|---|---|---|---|
WithService(s) |
string |
"" |
Service name attached to all logs |
WithMetadata(m) |
map[string]any |
nil |
Default metadata for all logs |
WithBatchSize(n) |
int |
50 |
Logs per batch (1-500) |
WithFlushInterval(d) |
time.Duration |
5s |
Auto-flush interval (100ms-60s) |
WithMaxQueueSize(n) |
int |
1000 |
Max queue size before dropping oldest (1-10000) |
WithMaxRetries(n) |
int |
3 |
Retry attempts for failed requests (0-10) |
WithCaptureSourceLocation(b) |
bool |
false |
Capture file/line info |
WithHTTPClient(c) |
*http.Client |
http.DefaultClient |
Custom HTTP client |
WithOnError(fn) |
func(*Error) |
nil |
Error callback |
WithOnFlush(fn) |
func(int) |
nil |
Flush callback (receives count) |
client, err := logwell.New(
"https://logs.example.com",
"lw_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
logwell.WithService("my-app"),
logwell.WithMetadata(logwell.M{"env": "production"}),
logwell.WithBatchSize(50),
logwell.WithFlushInterval(10*time.Second),
logwell.WithMaxQueueSize(5000),
logwell.WithMaxRetries(5),
logwell.WithCaptureSourceLocation(true),
logwell.WithOnError(func(err *logwell.Error) {
log.Printf("Logwell error: %v", err)
}),
logwell.WithOnFlush(func(count int) {
log.Printf("Flushed %d logs", count)
}),
)Five severity levels matching industry standards:
client.Debug("Detailed debugging info")
client.Info("Normal operational message")
client.Warn("Warning - something unusual")
client.Error("Error - operation failed")
client.Fatal("Fatal - unrecoverable error")All methods accept optional metadata maps:
client.Info("User action", logwell.M{
"userId": "123",
"action": "login",
"ipAddress": "192.168.1.1",
})Use logwell.M (shorthand for map[string]any) for structured metadata:
// Single metadata map
client.Info("Request processed", logwell.M{
"requestId": "abc-123",
"duration": 150,
"statusCode": 200,
})
// Multiple metadata maps (later maps override earlier)
client.Info("Event", logwell.M{"a": 1}, logwell.M{"b": 2})Set metadata that applies to all logs:
client, _ := logwell.New(
endpoint, apiKey,
logwell.WithMetadata(logwell.M{
"env": "production",
"version": "1.2.3",
}),
)
// All logs will include env and version
client.Info("Started") // includes env and versionCreate child loggers for request-scoped context:
// Create child with additional metadata
requestLogger := client.Child(
logwell.ChildWithMetadata(logwell.M{"requestId": "abc-123"}),
)
// All logs include requestId automatically
requestLogger.Info("Request received")
requestLogger.Info("Processing complete")
// Override service name for a child
dbLogger := client.Child(
logwell.ChildWithService("my-app-db"),
logwell.ChildWithMetadata(logwell.M{"component": "database"}),
)
dbLogger.Info("Query executed", logwell.M{"duration": 45})Child loggers:
- Share the parent's queue and transport (efficient batching)
- Inherit parent metadata (child metadata overrides on conflict)
- Can override the service name
- Can be shut down independently without affecting parent
Always call Shutdown before your application exits to ensure all queued logs are sent:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := client.Shutdown(ctx); err != nil {
log.Printf("Failed to shutdown cleanly: %v", err)
}Force an immediate flush without shutting down:
ctx := context.Background()
if err := client.Flush(ctx); err != nil {
log.Printf("Flush failed: %v", err)
}// Handle SIGTERM for graceful shutdown
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
go func() {
<-sigChan
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := client.Shutdown(ctx); err != nil {
log.Printf("Shutdown error: %v", err)
}
os.Exit(0)
}()Handle errors without blocking your application:
client, _ := logwell.New(
endpoint, apiKey,
logwell.WithOnError(func(err *logwell.Error) {
switch err.Code {
case logwell.ErrNetworkError:
log.Printf("Network issue: %v (will retry)", err)
case logwell.ErrUnauthorized:
log.Printf("Invalid API key: %v", err)
case logwell.ErrQueueOverflow:
log.Printf("Queue full, logs dropped: %v", err)
default:
log.Printf("Logwell error: %v", err)
}
}),
)| Code | Description | Retryable |
|---|---|---|
ErrNetworkError |
Network failure (connection, timeout) | Yes |
ErrUnauthorized |
Invalid API key (401) | No |
ErrValidationError |
Invalid log data (400) | No |
ErrRateLimited |
Too many requests (429) | Yes |
ErrServerError |
Server error (5xx) | Yes |
ErrQueueOverflow |
Queue full, oldest logs dropped | No |
ErrInvalidConfig |
Invalid configuration | No |
err := client.Flush(ctx)
if err != nil {
if logwellErr, ok := err.(*logwell.Error); ok {
fmt.Printf("Code: %s\n", logwellErr.Code)
fmt.Printf("Message: %s\n", logwellErr.Message)
fmt.Printf("StatusCode: %d\n", logwellErr.StatusCode)
fmt.Printf("Retryable: %t\n", logwellErr.Retryable)
fmt.Printf("Cause: %v\n", logwellErr.Cause)
}
}Enable automatic file and line number capture:
client, _ := logwell.New(
endpoint, apiKey,
logwell.WithCaptureSourceLocation(true),
)
client.Info("Something happened")
// Log includes: sourceFile: "main.go", lineNumber: 42Note: This uses
runtime.Caller()which has minor performance overhead. Disabled by default.
// Constructor
func New(endpoint, apiKey string, opts ...Option) (*Client, error)
// Log methods
func (c *Client) Debug(message string, metadata ...map[string]any)
func (c *Client) Info(message string, metadata ...map[string]any)
func (c *Client) Warn(message string, metadata ...map[string]any)
func (c *Client) Error(message string, metadata ...map[string]any)
func (c *Client) Fatal(message string, metadata ...map[string]any)
// Generic log with full control
func (c *Client) Log(entry LogEntry)
// Child logger
func (c *Client) Child(opts ...ChildOption) *Client
// Lifecycle
func (c *Client) Flush(ctx context.Context) error
func (c *Client) Shutdown(ctx context.Context) error// Log levels
const (
LevelDebug LogLevel = "debug"
LevelInfo LogLevel = "info"
LevelWarn LogLevel = "warn"
LevelError LogLevel = "error"
LevelFatal LogLevel = "fatal"
)
// Metadata shorthand
type M map[string]any
// Log entry structure
type LogEntry struct {
Level LogLevel
Message string
Timestamp string // Auto-generated if empty
Service string
Metadata M
SourceFile string
LineNumber int
}
// Ingest response
type IngestResponse struct {
Accepted int
Rejected int
Errors []string
}package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/Divkix/Logwell/sdks/go/logwell"
"github.com/google/uuid"
)
var logger *logwell.Client
func main() {
var err error
logger, err = logwell.New(
os.Getenv("LOGWELL_ENDPOINT"),
os.Getenv("LOGWELL_API_KEY"),
logwell.WithService("http-server"),
)
if err != nil {
log.Fatal(err)
}
http.HandleFunc("/", handler)
// Graceful shutdown
server := &http.Server{Addr: ":8080"}
go func() {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
<-sigChan
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
logger.Shutdown(ctx)
server.Shutdown(ctx)
}()
logger.Info("Server starting", logwell.M{"port": 8080})
log.Fatal(server.ListenAndServe())
}
func handler(w http.ResponseWriter, r *http.Request) {
start := time.Now()
requestID := uuid.New().String()
// Create request-scoped logger
reqLog := logger.Child(
logwell.ChildWithMetadata(logwell.M{"requestId": requestID}),
)
reqLog.Info("Request started", logwell.M{
"method": r.Method,
"path": r.URL.Path,
})
// Handle request...
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
reqLog.Info("Request completed", logwell.M{
"status": 200,
"duration": time.Since(start).Milliseconds(),
})
}- Go 1.21+
- No external dependencies
MIT License - see LICENSE for details.
