A flexible, structured logging package built on Go's slog with built-in support for trace IDs, request tracking, and performance monitoring.
- Structured Logging: Built on Go's standard
log/slogpackage - TraceID Support: First-class support for request tracing across your application
- Performance Tracking: Built-in memory and goroutine monitoring
- Flexible Configuration: Configure format, level, output destination, and more
- Method Chaining: Fluent API for adding context to your logs
- Multiple Output Formats: JSON or text format
- Context Integration: Extract trace IDs directly from context
- Zero Dependencies: Only uses Go standard library (except for testing)
go get github.com/Bparsons0904/goLoggerpackage main
import "github.com/Bparsons0904/goLogger"
func main() {
log := logger.New("my-service")
log.Info("Application started")
log.Debug("Debug information", "key", "value")
log.Warn("Warning message", "reason", "low memory")
log.Error("Error occurred", "error", err)
}import (
"log/slog"
"os"
"github.com/Bparsons0904/goLogger"
)
config := logger.Config{
Name: "my-service",
Format: logger.FormatJSON,
Level: slog.LevelDebug,
Writer: os.Stdout,
AddSource: true,
}
log := logger.NewWithConfig(config)
log.Info("Configured logger ready")log := logger.New("user-service")
// Add traceID to all subsequent logs
tracedLog := log.WithTraceID("req-123-abc")
tracedLog.Info("Processing request") // Includes traceID=req-123-abc
tracedLog.Debug("Cache hit") // Includes traceID=req-123-abc
tracedLog.Error("Database error") // Includes traceID=req-123-abc// In middleware: add traceID to context
ctx := logger.ContextWithTraceID(ctx, "req-123-abc")
// In handler/service: create logger from context
log := logger.New("user-service").TraceFromContext(ctx)
log.Info("User logged in") // Automatically includes traceID=req-123-abc// Add traceID with custom key
ctx := logger.ContextWithTraceIDName(ctx, "x-request-id", "req-456-def")
// Extract using custom key
log := logger.New("api-service").TraceFromContextName(ctx, "x-request-id")
log.Info("API call processed") // Includes traceID=req-456-defpackage middleware
import (
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
"github.com/Bparsons0904/goLogger"
)
func TraceID() fiber.Handler {
return func(c *fiber.Ctx) error {
// Get or generate trace ID
traceID := c.Get("X-Trace-ID")
if traceID == "" {
traceID = uuid.New().String()
}
// Add to response header
c.Set("X-Trace-ID", traceID)
// Add to context
ctx := logger.ContextWithTraceID(c.Context(), traceID)
c.SetUserContext(ctx)
return c.Next()
}
}
// Usage in handler
func HandleRequest(c *fiber.Ctx) error {
log := logger.New("api").TraceFromContext(c.Context())
log.Info("Request received", "path", c.Path())
// All logs will include the traceID
return c.JSON(fiber.Map{"status": "ok"})
}package middleware
import (
"context"
"net/http"
"github.com/google/uuid"
"github.com/Bparsons0904/goLogger"
)
func TraceIDMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Get or generate trace ID
traceID := r.Header.Get("X-Trace-ID")
if traceID == "" {
traceID = uuid.New().String()
}
// Add to response
w.Header().Set("X-Trace-ID", traceID)
// Add to context
ctx := logger.ContextWithTraceID(r.Context(), traceID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// Usage in handler
func HandleRequest(w http.ResponseWriter, r *http.Request) {
log := logger.New("api").TraceFromContext(r.Context())
log.Info("Request received", "path", r.URL.Path)
// All logs will include the traceID
}The logger supports method chaining for adding contextual information:
log := logger.New("user-service").
TraceFromContext(ctx).
File("user.handler.go").
Function("CreateUser")
log.Info("Creating new user", "email", email)
// Output includes: package=user-service, traceID=xxx, file=user.handler.go, function=CreateUserThe logger includes built-in performance monitoring capabilities for tracking memory usage and goroutine counts.
Add current memory statistics to a single log entry:
log := logger.New("batch-processor")
// Log with current memory metrics
log.WithMemoryStats().Info("Processing batch")Output includes:
memory_alloc_mb: Currently allocated memory in MBmemory_total_alloc_mb: Total allocated memory (cumulative) in MBmemory_sys_mb: Memory obtained from OS in MBmemory_num_gc: Number of completed GC cycles
Add current goroutine count to a log entry:
log := logger.New("worker-pool")
// Log with goroutine count
log.WithGoroutineCount().Info("Workers started")Track duration, memory delta, and goroutine delta for an operation:
log := logger.New("database-migration")
// Start timer with full performance tracking
done := log.TimerWithMetrics("user table migration")
// Perform migration...
// ... your code ...
done() // Logs all metricsOutput includes:
duration_ms: Operation duration in millisecondsduration: Human-readable duration stringmemory_start_mb: Memory at startmemory_end_mb: Memory at endmemory_delta_mb: Absolute memory changememory_delta_sign: "+" (increased), "-" (decreased), or "=" (no change)goroutines_start: Goroutine count at startgoroutines_end: Goroutine count at endgoroutines_delta: Change in goroutine count
Chain multiple performance methods:
log := logger.New("api-service").
TraceFromContext(ctx).
WithMemoryStats().
WithGoroutineCount()
log.Info("Request received")
// Includes traceID + memory stats + goroutine countfunc (s *Service) ProcessBatch(ctx context.Context, items []Item) error {
log := s.log.
TraceFromContext(ctx).
WithGoroutineCount().
Function("ProcessBatch")
log.Info("Starting batch", "size", len(items))
// Track full performance metrics
done := log.TimerWithMetrics("batch processing")
defer done()
for i, item := range items {
// Process item...
if i > 0 && i%1000 == 0 {
// Log progress with current memory
log.WithMemoryStats().Info("Progress",
"processed", i,
"remaining", len(items)-i,
)
}
}
return nil
}Example Output:
{
"time": "2025-01-18T10:30:00Z",
"level": "INFO",
"msg": "Operation completed with metrics",
"package": "batch-processor",
"traceID": "req-abc-123",
"operation": "batch processing",
"duration_ms": 15234,
"duration": "15.234s",
"memory_start_mb": 45.2,
"memory_end_mb": 123.8,
"memory_delta_mb": 78.6,
"memory_delta_sign": "+",
"goroutines_start": 12,
"goroutines_end": 15,
"goroutines_delta": 3
}log.Debug("Debug message", "key", "value")
log.Info("Info message", "key", "value")
log.Warn("Warning message", "key", "value")
log.Error("Error message", "key", "value")// Log and return error
err := log.Error("Something failed")
// Log with existing error
err = log.Err("Database connection failed", dbErr)
// Log without returning
log.Er("Operation failed", err)
// Simple error message
err = log.ErrMsg("Invalid input")// Add arbitrary key-value pairs
log.With("userID", 123, "role", "admin")
// Add file context
log.File("user.service.go")
// Add function context
log.Function("ProcessPayment")
// Add traceID
log.WithTraceID("trace-123")
log.TraceFromContext(ctx)
log.TraceFromContextName(ctx, "custom-key")
// Add performance metrics
log.WithMemoryStats()
log.WithGoroutineCount()// Step logging (info level)
log.Step("Processing batch 1 of 10")
// Timer (basic)
done := log.Timer("database migration")
// ... do work ...
done() // Logs duration only
// Timer with full performance metrics
done := log.TimerWithMetrics("database migration")
// ... do work ...
done() // Logs duration + memory delta + goroutine deltatype Config struct {
// Name is the logger identifier (e.g., package or service name)
Name string
// Format specifies the output format (FormatJSON or FormatText)
Format Format
// Level specifies the minimum log level (slog.LevelDebug, LevelInfo, LevelWarn, LevelError)
Level slog.Level
// Writer is the output destination (defaults to os.Stderr if nil)
Writer io.Writer
// AddSource adds source code position to log output
AddSource bool
}logger.FormatJSON- JSON structured logs (default)logger.FormatText- Human-readable text format
slog.LevelDebug- Detailed diagnostic informationslog.LevelInfo- Informational messages (default)slog.LevelWarn- Warning messagesslog.LevelError- Error messages
{
"time": "2025-01-18T10:30:00Z",
"level": "INFO",
"msg": "User logged in",
"package": "user-service",
"traceID": "req-123-abc",
"file": "user.handler.go",
"function": "HandleLogin",
"userID": 42,
"email": "user@example.com"
}2025-01-18T10:30:00Z INFO User logged in package=user-service traceID=req-123-abc file=user.handler.go function=HandleLogin userID=42 email=user@example.com
- Create logger instances at initialization: Create one logger per service/package and reuse it
type UserService struct {
log logger.Logger
}
func NewUserService() *UserService {
return &UserService{
log: logger.New("user-service"),
}
}- Use TraceFromContext in handlers: Extract trace ID from context in your HTTP handlers
func (s *UserService) CreateUser(ctx context.Context, req CreateUserRequest) error {
log := s.log.TraceFromContext(ctx)
log.Info("Creating user", "email", req.Email)
// ...
}- Chain context early: Add file/function context early in method chains
log := s.log.
TraceFromContext(ctx).
File("user.service.go").
Function("CreateUser")- Use structured logging: Always use key-value pairs instead of string concatenation
// Good
log.Info("User created", "userID", user.ID, "email", user.Email)
// Avoid
log.Info(fmt.Sprintf("User created: %s (%s)", user.ID, user.Email))- Leverage timers: Use the built-in timer for performance tracking
done := log.Timer("database migration")
defer done()
// ... perform migration ...The logger automatically detects test mode and discards output during tests. You can also create test loggers:
func TestMyFunction(t *testing.T) {
log := logger.NewWithConfig(logger.Config{
Name: "test",
Format: logger.FormatText,
Writer: io.Discard, // Or use a buffer to capture logs
})
// Your test code
}This project is licensed under the MIT License - see the LICENSE file for details.