A high-performance structured logging library for Go based on zerolog, designed to provide a clean API, efficient logs, and a fluid interface for your applications.
Current version: v0.1.1
- Added
SetServiceNamemethod to dynamically update service name after logger creation
- Initial release
- Basic logging functionality
- Structured logging support
- Environment variable configuration
- Builder pattern for logger creation
- High Performance: Optimized for speed and low memory usage
- Structured JSON Logs: Ready for systems like ELK, Loki, or Graylog
- Fluid Interface: Clean, chainable API for improved developer experience
- Multiple Log Styles: Support for both structured and printf-style logs
- Service Identification: Automatic tagging of logs with service name
- Environment Variable Configuration: Simple setup in different environments
- Pretty Format: Human-readable output for development environments
- Customizable Timestamp Format: Flexible time formatting options
- Context Support: Add structured fields to all logs
- Production & Development Modes: Pre-configured settings for different environments
go get -u github.com/jdroa1998/easy-loggerpackage main
import (
"errors"
"github.com/jdroa1998/easy-logger/logger"
)
func main() {
// Create a logger with default configuration
log := logger.New(logger.DefaultConfig())
// Log a simple message
log.InfoMsg("Application started")
// Log with formatting
log.Info().Msg("Server listening on port %d", 8080)
// Log with structured fields
log.Info().
Str("service", "api").
Int("port", 8080).
Bool("active", true).
Msg("Server configured")
// Log errors
err := errors.New("connection refused")
if err != nil {
log.Error().
WithError(err).
Str("operation", "startup").
Msg("Failed to initialize service")
}
}A simple log in JSON format:
{"level":"info","service":"UNKNOWN-SERVICE","time":"2023-04-01T12:34:56Z","message":"Application started"}A structured log in JSON format:
{"level":"info","service":"api","port":8080,"active":true,"time":"2023-04-01T12:34:56Z","message":"Server configured"}A log in pretty format (for development):
12:34:56 INF Server configured service=api port=8080 active=true
An error log with additional context:
{"level":"error","service":"UNKNOWN-SERVICE","error":"connection refused","operation":"startup","time":"2023-04-01T12:34:56Z","message":"Failed to initialize service"}package main
import (
"os"
"time"
"github.com/jdroa1998/easy-logger/logger"
)
func main() {
// Create a logger with the builder pattern
log := logger.NewBuilder().
WithLevel(logger.DebugLevel).
WithPrettyPrint(true).
WithServiceName("my-service").
WithCaller(true).
Build()
// Use predefined configurations
prodLog := logger.NewBuilder().
Production().
WithServiceName("api-production").
Build()
devLog := logger.NewBuilder().
Development().
WithServiceName("api-development").
Build()
// Custom configuration
customLog := logger.NewBuilder().
WithLevel(logger.InfoLevel).
WithPrettyPrint(false).
WithCaller(true).
WithOutput(os.Stdout).
WithTimeFormat(time.RFC3339).
WithServiceName("custom-service").
Build()
// Use the loggers
log.InfoMsg("Logger configured with builder")
prodLog.InfoMsg("Production logger ready")
devLog.Debug().Msg("Value: %v", 42)
customLog.Info().Str("type", "custom").Msg("Custom logger initialized")
}Easy Logger is designed to be efficient with minimal overhead above the underlying zerolog library:
| Operation | Easy Logger | zerolog | Overhead |
|---|---|---|---|
| Simple Log | 150.9 ns/op, 32 B/op, 1 allocs/op | 103.8 ns/op, 0 B/op, 0 allocs/op | ~45% |
| Structured Log | 208.5 ns/op, 32 B/op, 1 allocs/op | 142.6 ns/op, 0 B/op, 0 allocs/op | ~46% |
| With Caller | 754.0 ns/op, 368 B/op, 5 allocs/op | 556.3 ns/op, 344 B/op, 3 allocs/op | ~35% |
The overhead is reasonable considering the additional convenience features provided.
Configure the logger easily with environment variables:
package main
import "github.com/jdroa1998/easy-logger/logger"
func main() {
// Initialize from environment variables
log := logger.NewFromEnv()
log.InfoMsg("Logger configured from environment")
}Available environment variables:
LOG_LEVEL: Log level (trace, debug, info, warn, error, fatal, panic)LOG_FORMAT: Log format (json, pretty)LOG_CALLER: Enable/disable caller information (true, false)SERVICE_NAME: Service name to add to all logs
Easy Logger supports multiple logging styles to fit different coding preferences:
log.InfoMsg("User authenticated")
log.ErrorMsg("Database error: %v", err)log.Info().
Str("user", "admin").
Int("id", 1234).
Bool("active", true).
Msg("User logged in")
log.Error().
WithError(err).
Str("query", "SELECT * FROM users").
Int("attempt", 3).
Msg("Database query failed")// Create a logger with predefined fields
reqLogger := log.WithFields(map[string]any{
"request_id": "req-123",
"ip": "192.168.1.10",
"user_agent": "Mozilla/5.0...",
})
// All logs from this logger will include those fields
reqLogger.InfoMsg("Request started")
reqLogger.Warn().Int("response_time_ms", 500).Msg("Slow response")The main logger instance that provides logging capabilities.
type Logger struct {
// Internal fields not exposed
}
// Methods
func (l *Logger) SetServiceName(name string) // Set service name after creation
func (l *Logger) ServiceName() string // Get current service nameProvides a fluid interface for creating structured logs.
type LogBuilder struct {
// Internal fields not exposed
}Represents log severity levels.
type Level int8
const (
TraceLevel Level = -1
DebugLevel Level = 0
InfoLevel Level = 1
WarnLevel Level = 2
ErrorLevel Level = 3
FatalLevel Level = 4
PanicLevel Level = 5
)New(cfg Config) *Logger: Create a new logger with the given configurationNewBuilder() *LoggerBuilder: Start building a logger with the builder patternNewFromEnv() *Logger: Create a logger configured from environment variablesDefault() *Logger: Create a logger with default settingsDevelopment() *Logger: Create a logger optimized for developmentProduction() *Logger: Create a logger optimized for production
Each log level has methods in two styles:
- Builder style:
logger.Debug(),logger.Info(), etc. - Direct style:
logger.DebugMsg(),logger.InfoMsg(), etc.
Available levels:
Trace:logger.Trace(),logger.TraceMsg()Debug:logger.Debug(),logger.DebugMsg()Info:logger.Info(),logger.InfoMsg()Warn:logger.Warn(),logger.WarnMsg()Error:logger.Error(),logger.ErrorMsg()Fatal:logger.Fatal(),logger.FatalMsg()(terminates the program)Panic:logger.Panic(),logger.PanicMsg()(causes a panic)
Str(key string, value string) *LogBuilder: Add a string fieldInt(key string, value int) *LogBuilder: Add an integer fieldBool(key string, value bool) *LogBuilder: Add a boolean fieldAddField(key string, value any) *LogBuilder: Add a generic fieldWithError(err error) *LogBuilder: Add an errorMsg(msg string, values ...any): Finalize the log with a message
WithFields(fields map[string]any) *Logger: Create a new logger with predefined fieldsWith() zerolog.Context: Access the underlying zerolog contextServiceName() string: Get the current service name
type Config struct {
Level Level // Minimum level to log
Pretty bool // Enable pretty (human-readable) output
WithCaller bool // Include caller information
Output io.Writer // Destination for logs
TimeFormat string // Format for timestamps
ServiceName string // Name to identify service in logs
}WithLevel(level Level) *LoggerBuilder: Set minimum log levelWithPrettyPrint(enabled bool) *LoggerBuilder: Enable/disable pretty formatWithCaller(enabled bool) *LoggerBuilder: Include caller informationWithOutput(output io.Writer) *LoggerBuilder: Set output destinationWithTimeFormat(format string) *LoggerBuilder: Set timestamp formatWithServiceName(name string) *LoggerBuilder: Set service nameDevelopment() *LoggerBuilder: Configure for development environmentProduction() *LoggerBuilder: Configure for production environmentBuild() *Logger: Create logger with configured settings
ParseLevel(levelStr string) (Level, error): Parse level from stringLevel.String() string: Convert level to stringDefaultConfig() Config: Get default configurationDefaultJSONFormatter() Formatter: Get default JSON formatterDefaultPrettyFormatter() Formatter: Get default pretty formatter
Always set a meaningful service name to identify the source of logs:
// During logger creation
logger := logger.NewBuilder().
WithServiceName("payment-service").
Build()
// Or after logger creation
logger.SetServiceName("payment-service")Prefer structured logging over simple messages when possible:
// Good: structured and searchable
log.Info().
Str("user", user.ID).
Int("items", cart.Count).
Float64("total", cart.Total).
Msg("Order completed")
// Less Useful: information buried in message
log.InfoMsg("User %s completed order with %d items for $%.2f", user.ID, cart.Count, cart.Total)Use consistent field names across your application:
// Consistent naming convention
log.Info().
Str("user_id", "123").
Str("order_id", "456").
Int("item_count", 5).
Msg("Order processed")Use appropriate log levels:
Trace: Extremely detailed information, useful for debugging complex sequence of eventsDebug: Detailed information, useful during developmentInfo: General operational information about system behaviorWarn: Potentially harmful situations that might require attentionError: Error conditions that should be investigatedFatal: Severe error conditions that cause terminationPanic: Catastrophic failures that require immediate attention
Use context loggers to maintain request context:
// Create a request-scoped logger
reqLogger := log.WithFields(map[string]any{
"request_id": requestID,
"user_id": userID,
})
// Pass it to other functions
processOrder(reqLogger, order)
// In other functions
func processOrder(log *logger.Logger, order Order) {
log.Info().
Str("order_id", order.ID).
Msg("Processing order")
}See the examples directory for more detailed usage examples.
This project is licensed under the MIT License - see the LICENSE file for details.