Service Logger Factory
Create standardized loggers across all microservices:package logging
import (
"os"
"github.com/drossan/go_logs"
"github.com/drossan/go_logs/async"
)
type ServiceLoggerConfig struct {
ServiceName string
Version string
Environment string
LogLevel go_logs.Level
}
func NewServiceLogger(cfg ServiceLoggerConfig) (go_logs.Logger, error) {
// Create base logger with JSON output
syncLogger, err := go_logs.New(
go_logs.WithLevel(cfg.LogLevel),
go_logs.WithFormatter(go_logs.NewJSONFormatter()),
go_logs.WithRotatingFile(
fmt.Sprintf("/var/log/%s.log", cfg.ServiceName),
100, 5,
),
go_logs.WithCommonRedaction(),
)
if err != nil {
return nil, err
}
// Wrap with async logger for performance
asyncLogger := async.Wrap(syncLogger, 10000)
// Add service-level fields that appear in all logs
serviceLogger := asyncLogger.With(
go_logs.String("service", cfg.ServiceName),
go_logs.String("version", cfg.Version),
go_logs.String("environment", cfg.Environment),
go_logs.String("hostname", getHostname()),
)
return serviceLogger, nil
}
func getHostname() string {
hostname, _ := os.Hostname()
return hostname
}
Distributed Tracing with Context Propagation
Propagate trace IDs across service boundaries:package main
import (
"context"
"net/http"
"github.com/drossan/go_logs"
)
// TracingMiddleware extracts or generates trace ID
func TracingMiddleware(logger go_logs.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Extract trace ID from incoming request header
traceID := r.Header.Get("X-Trace-ID")
if traceID == "" {
// Generate new trace ID if not present
traceID = generateTraceID()
}
spanID := generateSpanID()
// Add trace context
ctx := go_logs.WithTraceID(r.Context(), traceID)
ctx = go_logs.WithSpanID(ctx, spanID)
// Create request logger with tracing fields
reqLogger := logger.With(
go_logs.String("trace_id", traceID),
go_logs.String("span_id", spanID),
go_logs.String("method", r.Method),
go_logs.String("path", r.URL.Path),
)
// Log request start
reqLogger.LogCtx(ctx, go_logs.InfoLevel, "Request received")
// Add trace ID to response header for downstream services
w.Header().Set("X-Trace-ID", traceID)
// Store logger in context
ctx = context.WithValue(ctx, loggerKey, reqLogger)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
Inter-Service HTTP Client with Tracing
Propagate trace context when calling other services:package client
import (
"context"
"fmt"
"io"
"net/http"
"time"
"github.com/drossan/go_logs"
)
type ServiceClient struct {
baseURL string
httpClient *http.Client
logger go_logs.Logger
}
func NewServiceClient(baseURL string, logger go_logs.Logger) *ServiceClient {
return &ServiceClient{
baseURL: baseURL,
httpClient: &http.Client{
Timeout: 30 * time.Second,
},
logger: logger,
}
}
func (c *ServiceClient) Get(ctx context.Context, path string) ([]byte, error) {
url := c.baseURL + path
// Create request
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
}
// Propagate trace ID from context
if traceID := go_logs.GetTraceID(ctx); traceID != "" {
req.Header.Set("X-Trace-ID", traceID)
}
// Log outgoing request
c.logger.LogCtx(ctx, go_logs.InfoLevel, "Calling downstream service",
go_logs.String("url", url),
go_logs.String("method", "GET"),
)
start := time.Now()
resp, err := c.httpClient.Do(req)
duration := time.Since(start)
if err != nil {
c.logger.Error("Downstream service call failed",
go_logs.Err(err),
go_logs.String("url", url),
go_logs.Float64("duration_ms", float64(duration.Milliseconds())),
)
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
// Log response
c.logger.LogCtx(ctx, go_logs.InfoLevel, "Downstream service responded",
go_logs.String("url", url),
go_logs.Int("status_code", resp.StatusCode),
go_logs.Float64("duration_ms", float64(duration.Milliseconds())),
go_logs.Int("response_size", len(body)),
)
if resp.StatusCode >= 400 {
return body, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
}
return body, err
}
Complete Microservice Example
User service with distributed tracing:package main
import (
"context"
"encoding/json"
"net/http"
"time"
"github.com/drossan/go_logs"
)
type UserService struct {
logger go_logs.Logger
ordersClient *ServiceClient
}
func NewUserService(logger go_logs.Logger) *UserService {
return &UserService{
logger: logger,
ordersClient: NewServiceClient("http://orders-service:8081", logger),
}
}
func (s *UserService) GetUserWithOrders(ctx context.Context, userID string) (*UserResponse, error) {
logger := getLogger(ctx)
// Log service operation start
logger.Info("Fetching user with orders",
go_logs.String("user_id", userID),
)
// Fetch user from database
user, err := s.fetchUser(ctx, userID)
if err != nil {
logger.Error("Failed to fetch user",
go_logs.Err(err),
go_logs.String("user_id", userID),
)
return nil, err
}
logger.Debug("User fetched from database",
go_logs.String("user_id", userID),
go_logs.String("username", user.Username),
)
// Fetch orders from orders service (trace ID propagated)
orders, err := s.fetchOrders(ctx, userID)
if err != nil {
logger.Warn("Failed to fetch orders, continuing without them",
go_logs.Err(err),
go_logs.String("user_id", userID),
)
// Degrade gracefully - return user without orders
return &UserResponse{User: user, Orders: []Order{}}, nil
}
logger.Info("User with orders fetched successfully",
go_logs.String("user_id", userID),
go_logs.Int("order_count", len(orders)),
)
return &UserResponse{User: user, Orders: orders}, nil
}
func (s *UserService) fetchUser(ctx context.Context, userID string) (*User, error) {
logger := getLogger(ctx)
logger.Debug("Querying user database",
go_logs.String("user_id", userID),
)
// Simulate database query
time.Sleep(10 * time.Millisecond)
return &User{
ID: userID,
Username: "john_doe",
Email: "[email protected]",
}, nil
}
func (s *UserService) fetchOrders(ctx context.Context, userID string) ([]Order, error) {
// This call propagates trace_id via HTTP headers
body, err := s.ordersClient.Get(ctx, "/api/orders?user_id="+userID)
if err != nil {
return nil, err
}
var orders []Order
if err := json.Unmarshal(body, &orders); err != nil {
return nil, err
}
return orders, nil
}
type User struct {
ID string `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
}
type Order struct {
ID string `json:"id"`
Amount float64 `json:"amount"`
}
type UserResponse struct {
User *User `json:"user"`
Orders []Order `json:"orders"`
}
Service Mesh Integration
Log sidecar proxy information (Istio/Envoy):func ServiceMeshMiddleware(logger go_logs.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
reqLogger := logger.With(
// Istio/Envoy headers
go_logs.String("x_request_id", r.Header.Get("X-Request-ID")),
go_logs.String("x_b3_traceid", r.Header.Get("X-B3-TraceId")),
go_logs.String("x_b3_spanid", r.Header.Get("X-B3-SpanId")),
go_logs.String("x_forwarded_for", r.Header.Get("X-Forwarded-For")),
)
ctx := context.WithValue(r.Context(), loggerKey, reqLogger)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
Health Check Endpoint
Standardized health checks across services:type HealthChecker struct {
logger go_logs.Logger
serviceName string
version string
}
func (h *HealthChecker) HandleHealth(w http.ResponseWriter, r *http.Request) {
h.logger.Debug("Health check requested")
status := h.checkHealth()
w.Header().Set("Content-Type", "application/json")
if status.Healthy {
w.WriteHeader(http.StatusOK)
} else {
w.WriteHeader(http.StatusServiceUnavailable)
h.logger.Warn("Service unhealthy",
go_logs.Any("checks", status.Checks),
)
}
json.NewEncoder(w).Encode(status)
}
func (h *HealthChecker) checkHealth() HealthStatus {
checks := make(map[string]bool)
// Check database
checks["database"] = h.checkDatabase()
// Check downstream services
checks["orders_service"] = h.checkOrdersService()
allHealthy := true
for _, healthy := range checks {
if !healthy {
allHealthy = false
break
}
}
return HealthStatus{
Healthy: allHealthy,
Service: h.serviceName,
Version: h.version,
Timestamp: time.Now(),
Checks: checks,
}
}
type HealthStatus struct {
Healthy bool `json:"healthy"`
Service string `json:"service"`
Version string `json:"version"`
Timestamp time.Time `json:"timestamp"`
Checks map[string]bool `json:"checks"`
}
Complete Microservice Setup
package main
import (
"fmt"
"net/http"
"os"
"github.com/drossan/go_logs"
)
func main() {
// Initialize service logger
logger, _ := NewServiceLogger(ServiceLoggerConfig{
ServiceName: "user-service",
Version: "1.2.3",
Environment: os.Getenv("ENVIRONMENT"),
LogLevel: go_logs.InfoLevel,
})
defer logger.Sync()
logger.Info("Service starting")
// Initialize service
userService := NewUserService(logger)
healthChecker := &HealthChecker{
logger: logger,
serviceName: "user-service",
version: "1.2.3",
}
// Setup routes
mux := http.NewServeMux()
mux.HandleFunc("/api/users/", func(w http.ResponseWriter, r *http.Request) {
handleGetUserWithOrders(userService, w, r)
})
mux.HandleFunc("/health", healthChecker.HandleHealth)
// Apply middleware chain
handler := ServiceMeshMiddleware(logger)(
TracingMiddleware(logger)(
LoggingMiddleware(logger)(mux),
),
)
// Start server
port := 8080
logger.Info("Service ready",
go_logs.Int("port", port),
)
http.ListenAndServe(fmt.Sprintf(":%d", port), handler)
}
Next Steps
OpenTelemetry Integration
Export logs to OTLP collectors
Production Setup
Complete production deployment guide