Set up uber-go/zap for high-performance structured logging in Go. Ship JSON logs to LogFlow with a custom WriteSyncer.
Zap is the fastest structured logging library for Go. It allocates zero bytes for common operations and outputs JSON by default — ideal for production logging.
This tutorial sets up Zap with JSON output and ships logs to LogFlow.
go get go.uber.org/zap
package main
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
"time"
)
func NewLogger() (*zap.Logger, error) {
config := zap.Config{
Level: zap.NewAtomicLevelAt(zap.InfoLevel),
Development: false,
Encoding: "json",
EncoderConfig: zapcore.EncoderConfig{
TimeKey: "timestamp",
LevelKey: "level",
MessageKey: "message",
CallerKey: "caller",
StacktraceKey: "stacktrace",
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05.000"),
EncodeCaller: zapcore.ShortCallerEncoder,
},
OutputPaths: []string{"stdout"},
ErrorOutputPaths: []string{"stderr"},
InitialFields: map[string]interface{}{
"service": os.Getenv("SERVICE_NAME"),
},
}
return config.Build()
}
Output:
{"timestamp":"2026-08-21 14:23:11.456","level":"info","message":"server.started","caller":"main.go:42","service":"api","port":8080}
func main() {
logger, _ := NewLogger()
defer logger.Sync()
// Basic logging
logger.Info("server.started", zap.Int("port", 8080))
// With multiple fields
logger.Info("request.handled",
zap.String("method", "GET"),
zap.String("path", "/api/users"),
zap.Int("status", 200),
zap.Duration("duration", 45*time.Millisecond),
)
// Error with context
logger.Error("payment.failed",
zap.String("order_id", "ORD-1234"),
zap.Error(err),
)
}
Zap also offers a "sugared" logger with a more relaxed API (slightly slower but easier to use):
sugar := logger.Sugar()
sugar.Infow("request.handled",
"method", "POST",
"path", "/api/orders",
"status", 201,
"duration_ms", 123,
)
sugar.Errorf("failed to connect to %s: %v", host, err)
Create middleware that adds request context to every log:
package middleware
import (
"context"
"net/http"
"time"
"github.com/google/uuid"
"go.uber.org/zap"
)
type ctxKey string
const loggerKey ctxKey = "logger"
func RequestLogger(base *zap.Logger) 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()
requestID := r.Header.Get("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()[:8]
}
// Create a child logger with request context
reqLogger := base.With(
zap.String("request_id", requestID),
zap.String("method", r.Method),
zap.String("path", r.URL.Path),
zap.String("ip", r.RemoteAddr),
)
// Store logger in context
ctx := context.WithValue(r.Context(), loggerKey, reqLogger)
// Wrap response writer to capture status
rw := &responseWriter{ResponseWriter: w, status: 200}
next.ServeHTTP(rw, r.WithContext(ctx))
reqLogger.Info("request.handled",
zap.Int("status", rw.status),
zap.Duration("duration", time.Since(start)),
)
})
}
}
// Get logger from request context
func Log(ctx context.Context) *zap.Logger {
if l, ok := ctx.Value(loggerKey).(*zap.Logger); ok {
return l
}
return zap.NewNop()
}
type responseWriter struct {
http.ResponseWriter
status int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.status = code
rw.ResponseWriter.WriteHeader(code)
}
Usage in handlers:
func handleCreateOrder(w http.ResponseWriter, r *http.Request) {
log := middleware.Log(r.Context())
log.Info("order.creating", zap.String("user_id", userID))
order, err := createOrder(r.Context(), req)
if err != nil {
log.Error("order.failed", zap.Error(err))
http.Error(w, "failed", 500)
return
}
log.Info("order.created", zap.String("order_id", order.ID))
}
Create a custom WriteSyncer that sends logs to LogFlow:
package logflow
import (
"bytes"
"encoding/json"
"net/http"
"os"
"sync"
"time"
)
type Writer struct {
apiKey string
url string
client *http.Client
mu sync.Mutex
buffer []json.RawMessage
}
func NewWriter() *Writer {
w := &Writer{
apiKey: os.Getenv("LOGFLOW_API_KEY"),
url: "https://api.getlogflow.com/v1/logs",
client: &http.Client{Timeout: 5 * time.Second},
buffer: make([]json.RawMessage, 0, 100),
}
// Flush every 2 seconds
go func() {
ticker := time.NewTicker(2 * time.Second)
for range ticker.C {
w.Flush()
}
}()
return w
}
func (w *Writer) Write(p []byte) (int, error) {
w.mu.Lock()
w.buffer = append(w.buffer, append(json.RawMessage{}, p...))
if len(w.buffer) >= 50 {
w.mu.Unlock()
w.Flush()
} else {
w.mu.Unlock()
}
return len(p), nil
}
func (w *Writer) Flush() {
w.mu.Lock()
if len(w.buffer) == 0 || w.apiKey == "" {
w.mu.Unlock()
return
}
batch := w.buffer
w.buffer = make([]json.RawMessage, 0, 100)
w.mu.Unlock()
body, _ := json.Marshal(batch)
req, _ := http.NewRequest("POST", w.url, bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+w.apiKey)
req.Header.Set("Content-Type", "application/json")
w.client.Do(req) // Fire and forget
}
func (w *Writer) Sync() error {
w.Flush()
return nil
}
Wire it into your logger:
import (
"go.uber.org/zap/zapcore"
"io"
"os"
)
func NewLogger() *zap.Logger {
encoder := zapcore.NewJSONEncoder(encoderConfig)
// Write to both stdout and LogFlow
logflowWriter := logflow.NewWriter()
multiWriter := zapcore.NewMultiWriteSyncer(
zapcore.AddSync(os.Stdout),
zapcore.AddSync(logflowWriter),
)
core := zapcore.NewCore(encoder, multiWriter, zap.InfoLevel)
return zap.New(core, zap.AddCaller())
}
Run your Go application and make some requests. Then search in LogFlow:
service:api level:info
You should see structured logs with all fields searchable.
| Library | Speed | API Style | Stdlib? |
|---|---|---|---|
| Zap | Fastest | Typed fields | No |
| Zerolog | Very fast | Chained builder | No |
| Slog | Fast enough | Typed fields | Yes (Go 1.21+) |
If you're starting a new project, also consider zerolog or Go's standard log/slog.
Free plan available. No credit card required. Up and running in 2 minutes.
Get started freeLogging in Python with LogFlow
Add production-ready structured logging to any Python application and ship logs to LogFlow.
Structured Logging in Flask
Add production-ready structured logging to any Flask app in under 10 minutes.
Structured Logging in Laravel
Add production-ready structured logging to Laravel and ship logs to LogFlow.