logx

package
v0.0.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 6, 2026 License: MIT Imports: 10 Imported by: 0

README

Logging Package (logx)

The logx package provides a structured, high-performance logging system built on top of zap. It supports multiple transports, automatic log interception, development and production modes, and rich contextual information.

Features

  • Structured Logging: Built on zap for high-performance structured logs
  • Multiple Transports: Console, custom transports, or your own implementation
  • Log Levels: Debug, Info, Warn, Error, Fatal with automatic level management
  • Development/Production Modes: Different defaults for each environment
  • Context Enrichment: Automatically includes service, instance ID, and state in logs
  • Log Interception: Capture logs from other libraries and redirect them
  • Child Loggers: Create loggers with additional context fields

Quick Start

Basic Usage
package main

import (
	"context"
	
	"github.com/atlastore/belt/logx"
	"github.com/google/uuid"
	"go.uber.org/zap"
)

func main() {
	ctx := context.Background()
	
	// Create a logger in development mode
	log := logx.New(
		ctx,
		logx.NewConfig(logx.Development, "my-service", uuid.NewString()),
		&logx.ConsoleTransport{},
	)
	defer log.Close()
	
	// Log messages
	log.Info("Service starting")
	log.Debug("Debug information", zap.String("config", "loaded"))
	log.Warn("Warning message", zap.Int("retry_count", 3))
	log.Error("Error occurred", zap.Error(err))
}
Production Configuration
// Production logger with JSON output
log := logx.New(
	ctx,
	logx.NewConfig(logx.Production, "my-service", uuid.NewString()),
	&logx.ConsoleTransport{}, // Outputs structured JSON
)

// Production mode:
// - Only Info, Warn, Error, Fatal levels (no Debug)
// - JSON formatted output
// - Stack traces on errors
// - Automatic caller information
Development Configuration
// Development logger with colored, human-readable output
log := logx.New(
	ctx,
	logx.NewConfig(logx.Development, "my-service", uuid.NewString()),
	&logx.ConsoleTransport{},
)

// Development mode:
// - All log levels including Debug
// - Colored, human-readable output
// - Stack traces on errors
// - Caller information

Configuration

Logger Config
type Config struct {
	State      State  // Development or Production
	Service    string // Service name
	InstanceID string // Unique instance identifier
}

cfg := logx.NewConfig(logx.Production, "user-service", "instance-123")
Log Levels
log.Debug("Verbose debugging info", zap.Any("data", complexObj))
log.Info("Informational message", zap.String("event", "user_login"))
log.Warn("Warning message", zap.String("reason", "deprecated_api"))
log.Error("Error occurred", zap.Error(err))
log.Fatal("Fatal error, exiting", zap.Error(err)) // Calls os.Exit(1)

Advanced Features

Child Loggers

Create loggers with additional context that persists across all log calls:

// Parent logger
log := logx.New(ctx, cfg, &logx.ConsoleTransport{})

// Child logger with component context
dbLogger := log.With(
	zap.String("component", "database"),
	zap.String("pool", "primary"),
)

dbLogger.Info("Query executed", zap.Duration("duration", 15*time.Millisecond))
// Output includes: component=database, pool=primary

// Another child for cache operations
cacheLogger := log.With(zap.String("component", "cache"))
cacheLogger.Info("Cache hit", zap.String("key", "user:123"))
Custom Transports

Implement your own transport to send logs anywhere:

type MyTransport struct {
	// Your fields
}

func (t *MyTransport) Write(entry logx.Entry) {
	// Send to your logging backend
	// - Elasticsearch
	// - CloudWatch
	// - Datadog
	// - Your own system
	
	fmt.Printf("[%s] %s: %s\n", entry.Level, entry.Service, entry.Message)
}

// Use it
log := logx.New(ctx, cfg, &MyTransport{})
No-Op Logger

For testing or when you want to disable logging:

log := logx.New(ctx, cfg, &logx.NoopTransport{})

// All log calls are silently ignored
log.Info("This won't be logged")

Log Output Examples

Development Mode Output
2026-01-04T15:30:45.123+0200	INFO	user-service	User logged in	{"user_id": "user-123", "ip": "192.168.1.100"}
2026-01-04T15:30:46.456+0200	WARN	user-service	API rate limit approaching	{"current": 950, "limit": 1000}
2026-01-04T15:30:47.789+0200	ERROR	user-service	Database query failed	{"error": "connection timeout", "query": "SELECT * FROM users"}
Production Mode Output (JSON)
{
  "level": "info",
  "timestamp": "2026-01-04T15:30:45.123Z",
  "service": "user-service",
  "instance_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "state": "production",
  "message": "User logged in",
  "user_id": "user-123",
  "ip": "192.168.1.100"
}

Integration with Servers

The logx logger integrates seamlessly with the server package:

import (
	"github.com/atlastore/belt/logx"
	"github.com/atlastore/belt/server"
)

log := logx.New(ctx, cfg, &logx.ConsoleTransport{})

// Server automatically logs all requests
srv := server.NewServer(server.HTTP, log, options...)

Automatic request logs:

{
  "level": "info",
  "service": "api-gateway",
  "message": "HTTP request",
  "method": "GET",
  "path": "/api/users/123",
  "status": 200,
  "duration": "12.5ms",
  "ip": "192.168.1.100"
}

Best Practices

1. Use Structured Fields
// Good - structured and searchable
log.Info("User created",
	zap.String("user_id", userID),
	zap.String("email", email),
	zap.Time("created_at", time.Now()),
)

// Bad - unstructured string concatenation
log.Info(fmt.Sprintf("User %s created with email %s", userID, email))
2. Use Appropriate Log Levels
// Debug - detailed information for debugging
log.Debug("Cache lookup", zap.String("key", key), zap.Bool("hit", true))

// Info - general informational messages
log.Info("Server started", zap.Int("port", 8080))

// Warn - warning conditions that should be addressed
log.Warn("API deprecated", zap.String("endpoint", "/api/v1/users"))

// Error - error conditions that need attention
log.Error("Failed to send email", zap.Error(err), zap.String("recipient", email))

// Fatal - severe errors, program will exit
log.Fatal("Cannot connect to database", zap.Error(err))
3. Create Child Loggers for Components
// One logger per major component
dbLogger := log.With(zap.String("component", "database"))
cacheLogger := log.With(zap.String("component", "cache"))
apiLogger := log.With(zap.String("component", "api"))

// Use throughout your codebase
dbLogger.Info("Connection pool initialized", zap.Int("size", 10))
cacheLogger.Warn("Cache eviction", zap.Int("items", 100))
4. Include Relevant Context
// Include request IDs for tracing
requestLogger := log.With(zap.String("request_id", requestID))
requestLogger.Info("Processing request")
// ... do work ...
requestLogger.Info("Request completed", zap.Duration("duration", elapsed))
5. Use Error Wrapping
if err := database.Query(ctx, sql); err != nil {
	log.Error("Database query failed",
		zap.Error(err),
		zap.String("query", sql),
		zap.String("operation", "user_lookup"),
	)
	return fmt.Errorf("failed to lookup user: %w", err)
}

Performance Considerations

  • Zero Allocation: The underlying zap logger uses zero-allocation techniques
  • Sampling: In production, consider using zap's sampling to reduce log volume
  • Async Writes: Transports can implement async writing for better performance
  • Level Checks: Use log.Config().State == logx.Development to conditionally include expensive debug logic

Testing

Use NoopTransport in tests to avoid log noise:

func TestMyFunction(t *testing.T) {
	log := logx.New(
		context.Background(),
		logx.NewConfig(logx.Development, "test", "test-instance"),
		&logx.NoopTransport{},
	)
	
	// Test your code
	myFunction(log)
}

License

MIT License - see LICENSE.txt for details.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	State      State
	NumWorkers int
	Service    string
	InstanceID string
}

Config is the internal config for setup for the logger.

func NewConfig

func NewConfig(state State, service, instanceID string, numWorkers ...int) Config

NewConfig creates a new instance of the Config struct with the provided state, service, instanceID and optional numWorkers. The state is which mode the logger should be used with. Service is what kind of service is being used InstanceID

type ConsoleTransport

type ConsoleTransport struct{}

func (*ConsoleTransport) Send

func (t *ConsoleTransport) Send(entry LogEntry) error

type LogEntry

type LogEntry struct {
	Level      zapcore.Level
	Time       time.Time
	LoggerName string
	Message    string
	Caller     zapcore.EntryCaller
	Stack      string
	Fields     map[string]any
	Service    string
	InstanceID string
	State      string
}

func (*LogEntry) MarshalFields

func (le *LogEntry) MarshalFields() ([]byte, error)

type Logger

type Logger struct {
	// contains filtered or unexported fields
}

A Logger provides fast, leveled, structured logging. All methods are safe for concurrent use. The Logger extends the zap.Logger with the ability to send all logs to a sing

func New

func New(ctx context.Context, config Config, transport Transport, options ...zap.Option) *Logger

For sample code, see the package-level AdvancedConfiguration example.

func (*Logger) Close

func (lg *Logger) Close() error

Close syncs and flushes any buffered log entries while also closing all log workers.

func (*Logger) Config

func (lg *Logger) Config() Config

Config returns the underlying config

func (*Logger) DPanic

func (lg *Logger) DPanic(msg string, fields ...zapcore.Field)

DPanic logs a message at DPanicLevel. The message includes any fields passed at the log site, as well as any fields accumulated on the logger.

If the logger is in development mode, it then panics (DPanic means "development panic"). This is useful for catching errors that are recoverable, but shouldn't ever happen.

func (*Logger) Debug

func (lg *Logger) Debug(msg string, fields ...zapcore.Field)

Debug logs a message at DebugLevel. The message includes any fields passed at the log site, as well as any fields accumulated on the logger.

func (*Logger) DroppedCount

func (lg *Logger) DroppedCount() int64

DroppedCount returns the amount of log entries that were not sent with the transport

func (*Logger) Error

func (lg *Logger) Error(msg string, fields ...zapcore.Field)

Error logs a message at ErrorLevel. The message includes any fields passed at the log site, as well as any fields accumulated on the logger.

func (*Logger) Fatal

func (lg *Logger) Fatal(msg string, fields ...zapcore.Field)

Fatal logs a message at FatalLevel. The message includes any fields passed at the log site, as well as any fields accumulated on the logger.

The logger then calls os.Exit(1), even if logging at FatalLevel is disabled.

func (*Logger) Info

func (lg *Logger) Info(msg string, fields ...zapcore.Field)

Info logs a message at InfoLevel. The message includes any fields passed at the log site, as well as any fields accumulated on the logger.

func (*Logger) Level

func (lg *Logger) Level() zapcore.Level

Level reports the minimum enabled level for this logger.

For NopLoggers, this is zapcore.InvalidLevel.

func (*Logger) Log

func (lg *Logger) Log(lvl zapcore.Level, msg string, fields ...zapcore.Field)

Log logs a message at the specified level. The message includes any fields passed at the log site, as well as any fields accumulated on the logger. Any Fields that require evaluation (such as Objects) are evaluated upon invocation of Log.

func (*Logger) Panic

func (lg *Logger) Panic(msg string, fields ...zapcore.Field)

Panic logs a message at PanicLevel. The message includes any fields passed at the log site, as well as any fields accumulated on the logger.

The logger then panics, even if logging at PanicLevel is disabled.

func (*Logger) Warn

func (lg *Logger) Warn(msg string, fields ...zapcore.Field)

Warn logs a message at WarnLevel. The message includes any fields passed at the log site, as well as any fields accumulated on the logger.

func (*Logger) With added in v0.0.2

func (lg *Logger) With(fields ...zap.Field) *Logger

type NOOPTransport

type NOOPTransport struct{}

func (*NOOPTransport) Send

func (t *NOOPTransport) Send(_ LogEntry) error

type State

type State int

State is the mode the logger is being used in.

const (
	// The logger is being used in Development mode.
	Development State = iota + 1
	// The logger is being used in Production mode.
	Production
)

func (State) String added in v0.0.2

func (s State) String() string

type Transport

type Transport interface {
	Send(entry LogEntry) error
}

Transport abstracts sending a log entry to an external system.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL