aurganize-backend/backend/pkg/logger/logger.go

209 lines
5.0 KiB
Go

package logger
import (
"io"
"os"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
const (
LevelDebug = "debug"
LevelInfo = "info"
LevelWarn = "warn"
LevelError = "error"
)
// Init initializes the global logger with environment-specific settings.
// Call this once at application startup.
//
// Environment determines output format:
// - development: Pretty console output with colors
// - staging/production: JSON output for log aggregation
//
// Example:
//
// logger.Init("development")
// log.Info().Msg("Application started")
func Init(enviroment string) {
// Configuring the time format for loggin
// Unix style timestamp in production for efficiency
// Humar-readable in development
if enviroment == "production" {
// This is the unixtimestamp format used in production for efficiency : 1732632645
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
} else {
zerolog.TimeFieldFormat = time.RFC3339
// This is format for human readable format : 2025-11-26T10:30:45-05:00
}
var output io.Writer = os.Stdout
if enviroment == "development" {
output = zerolog.ConsoleWriter{
Out: os.Stdout,
TimeFormat: time.RFC3339, // 2025-11-26T10:30:45-05:00
NoColor: false, // Enabling Colours
}
}
// Production uses default JSON output to stdout
// Set global logger
// Caller() : this caused adtional overhead as runtime.Caller() is called, which is worth
// for debugging value, should disable in production
log.Logger = zerolog.New(output).
With().
Timestamp().
Caller(). // This line is to add file and line number information into the log
Logger()
// set global log level
// Debug (most verbose)
// ↓
// Info
// ↓
// Warn
// ↓
// Error
// ↓
// Fatal (least verbose)
switch enviroment {
case "production":
zerolog.SetGlobalLevel(zerolog.InfoLevel)
case "UAT":
zerolog.SetGlobalLevel(zerolog.InfoLevel)
case "staging":
zerolog.SetGlobalLevel(zerolog.InfoLevel)
case "test":
zerolog.SetGlobalLevel(zerolog.DebugLevel)
case "development":
zerolog.SetGlobalLevel(zerolog.DebugLevel)
}
// Log initialization
log.Info().
Str("environment", enviroment).
Str("log_level", zerolog.GlobalLevel().String()).
Msg("logger initialized")
}
func InitWithLevel(environment, level string) {
Init(environment)
switch level {
case LevelDebug:
zerolog.SetGlobalLevel(zerolog.DebugLevel)
case LevelInfo:
zerolog.SetGlobalLevel(zerolog.InfoLevel)
case LevelWarn:
zerolog.SetGlobalLevel(zerolog.WarnLevel)
case LevelError:
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
default:
log.Warn().
Str("provided_level", level).
Str("using_level", zerolog.GlobalLevel().String()).
Msg("Invalid log level, using default")
}
log.Info().
Str("environment", environment).
Str("log_level", zerolog.GlobalLevel().String()).
Msg("Logger initialized with custom level")
}
// GetLogger returns the global logger instance.
// Use this to get a logger with additional context.
//
// Example :
//
// logger := logger.GetLogger().
// With().
// Str("module","auth").
// Logger()
// logger.Info().Msg("Auth module started")
func GetLogger() *zerolog.Logger {
return &log.Logger
}
// WithContext returns a logger with additional context fields.
// Useful for adding request-specific context.
//
// Example:
//
// contextLogger := logger.WithContext(map[string]interface{}{
// "request_id": "abc-123",
// "user_id": "user-456",
// })
// contextLogger.Info().Msg("Processing request")
func WithContext(fields map[string]interface{}) *zerolog.Logger {
logger := log.Logger
for key, value := range fields {
logger = logger.With().Interface(key, value).Logger()
}
return &logger
}
// Example Usage log functions (for familiarizing its usage)
// LogDebug logs debug information (these are verbose, development only)
func ExampleDebug() {
log.Debug().
Str("function", "ExampleDebug").
Int("iteration", 1).
Msg("Debug information")
}
func ExampleInfo() {
log.Info().
Str("user_id", "123").
Str("reason", "invalid_token").
Msg("User logged in successfully")
}
func ExampleError() {
log.Error().
Err(nil). // we add the actual error here
Str("user_id", "123").
Str("operation", "database_query").
Msg("Failed to fetch user data")
}
// Log Fatal logs fatal erros and exits the application
// user sparingly - only for unrecoverable errors
func ExmapleFatal() {
// log.Fatal().
// Err(err).
// Msg("Cannot connect to database")
// -- Application exits after this
}
// Log with fields demonstrated logging mutliple fields
func ExampleWithFields() {
log.Info().
Str("user_id", "123").
Str("email", "user@example.com").
Int("login_attemtps", 3).
Bool("success", true).
Dur("duration", 150*time.Millisecond).
Msg("login completed")
}
// Log with SubLogger demonstrates creating sub-loggers
func ExampleSubLogger() {
// create a sub-logger for a specific module
authLogger := log.With().
Str("module", "auth").
Str("version", "v1").
Logger()
authLogger.Info().Msg("Auth module initialized")
authLogger.Debug().Msg("Loading auth configuration")
}