opcotech/elemo

View on GitHub
internal/pkg/log/log.go

Summary

Maintainability
A
0 mins
Test Coverage
F
56%
package log

import (
    "context"
    "errors"
    "sync"

    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"

    "github.com/opcotech/elemo/internal/pkg"
)

var (
    ErrNoLogger            = errors.New("no logger")             // the logger is missing
    ErrInvalidLogLevel     = errors.New("invalid log level")     // invalid log level
    ErrInvalidLoggerConfig = errors.New("invalid logger config") // invalid logger config

    globalLogger, _ = zap.NewProduction()
    loggerLock      sync.Mutex
)

// ZapLogger is a type alias for zap.Logger.
type ZapLogger = zap.Logger

// Logger defines the interface for the application logger.
type Logger interface {
    Sugar() *zap.SugaredLogger
    Named(s string) *zap.Logger
    WithOptions(opts ...zap.Option) *zap.Logger
    With(fields ...zap.Field) *zap.Logger
    Check(lvl zapcore.Level, msg string) *zapcore.CheckedEntry
    Log(lvl zapcore.Level, msg string, fields ...zap.Field)
    Debug(msg string, fields ...zap.Field)
    Info(msg string, fields ...zap.Field)
    Warn(msg string, fields ...zap.Field)
    Error(msg string, fields ...zap.Field)
    DPanic(msg string, fields ...zap.Field)
    Panic(msg string, fields ...zap.Field)
    Fatal(msg string, fields ...zap.Field)
    Sync() error
    Core() zapcore.Core
}

// ConfigureLogger configures the logger then returns it.
func ConfigureLogger(level string) (Logger, error) {
    var err error

    logLevel, err := zap.ParseAtomicLevel(level)
    if err != nil {
        return nil, errors.Join(ErrInvalidLogLevel, err)
    }

    zapConf := zap.NewProductionConfig()
    zapConf.Level = logLevel

    loggerLock.Lock()
    defer loggerLock.Unlock()

    if globalLogger, err = zapConf.Build(); err != nil {
        return nil, errors.Join(ErrInvalidLoggerConfig, err)
    }

    globalLogger = globalLogger.Named("root")

    return globalLogger, nil
}

// DefaultLogger returns the global logger.
func DefaultLogger() Logger {
    return globalLogger
}

// SimpleLogger is used to log the message where only arguments are available.
type SimpleLogger struct {
    logger Logger
}

func (l *SimpleLogger) log(level zapcore.Level, args ...interface{}) {
    message := args[len(args)-1].(string)

    logArgs := make([]zap.Field, (len(args)-1)/2)
    for i, j := 1, 0; i < len(args)-1; i += 2 {
        logArgs[j] = zap.Any(args[i].(string), args[i+1])
        j++
    }

    l.logger.Log(level, message, logArgs...)
}

func (l *SimpleLogger) Debug(args ...interface{}) {
    l.log(zap.DebugLevel, args...)
}

func (l *SimpleLogger) Info(args ...interface{}) {
    l.log(zap.InfoLevel, args...)
}

func (l *SimpleLogger) Warn(args ...interface{}) {
    l.log(zap.WarnLevel, args...)
}

func (l *SimpleLogger) Error(args ...interface{}) {
    l.log(zap.ErrorLevel, args...)
}

func (l *SimpleLogger) Fatal(args ...interface{}) {
    l.log(zap.FatalLevel, args...)
}

// NewSimpleLogger returns a new SimpleLogger.
func NewSimpleLogger(logger Logger) *SimpleLogger {
    return &SimpleLogger{logger: logger}
}

// WithContext returns a new context with the logger. If the logger is not
// provided, it returns the context with the global logger assigned.
func WithContext(ctx context.Context, logger Logger) context.Context {
    ctxLogger := logger
    if ctxLogger == nil {
        ctxLogger = globalLogger
    }

    return context.WithValue(ctx, pkg.CtxKeyLogger, ctxLogger)
}

// FromContext returns the logger from the context. If the logger is not
// found in the context, it returns the global logger.
func FromContext(ctx context.Context) Logger {
    if ctxLogger, ok := ctx.Value(pkg.CtxKeyLogger).(Logger); ok {
        return ctxLogger
    }

    return globalLogger
}

// Log logs the message with the given level.
// NOTE: This may log sensitive information.
// TODO: Implement a log filter to filter out sensitive information.
func Log(ctx context.Context, level zapcore.Level, message string, fields ...zap.Field) {
    commonFields := make([]zap.Field, 0)
    logger := FromContext(ctx)
    logger.Log(level, message, append(fields, commonFields...)...)
}

// Debug logs the message with the debug level.
func Debug(ctx context.Context, message string, fields ...zap.Field) {
    Log(ctx, zapcore.DebugLevel, message, fields...)
}

// Info logs the message with the info level.
func Info(ctx context.Context, message string, fields ...zap.Field) {
    Log(ctx, zapcore.InfoLevel, message, fields...)
}

// Warn logs the message with the warn level.
func Warn(ctx context.Context, message string, fields ...zap.Field) {
    Log(ctx, zapcore.WarnLevel, message, fields...)
}

// Error logs the message with the error level.
func Error(ctx context.Context, err error, fields ...zap.Field) {
    Log(ctx, zapcore.ErrorLevel, err.Error(), append(fields, zap.Error(err))...)
}

// Fatal logs the message with the fatal level.
func Fatal(ctx context.Context, message string, fields ...zap.Field) {
    Log(ctx, zapcore.FatalLevel, message, fields...)
}

// Panic logs the message with the panic level.
func Panic(ctx context.Context, message string, fields ...zap.Field) {
    Log(ctx, zapcore.PanicLevel, message, fields...)
}