From 3f07a4b6c745707f135a7a97e93b0fa770b67873 Mon Sep 17 00:00:00 2001 From: Bobby Date: Fri, 6 Mar 2026 22:15:09 +0530 Subject: Add configuration and logging modules with TOML support - Implement configuration management in the config package - Define constants and types for server and mailbox configurations - Create functions for loading and parsing configuration files - Introduce logging functionality with customizable log levels and formats - Add error handling and messages for configuration and logging operations - Include TOML utilities for default value application and content marshaling --- utils/collections/types.go | 3 ++ utils/errors/errors.go | 14 ++++++++ utils/logger/constants.go | 25 ++++++++++++++ utils/logger/functions.go | 53 +++++++++++++++++++++++++++++ utils/logger/logger.go | 83 ++++++++++++++++++++++++++++++++++++++++++++++ utils/logger/types.go | 11 ++++++ utils/toml/defaults.go | 64 +++++++++++++++++++++++++++++++++++ utils/toml/load.go | 21 ++++++++++++ utils/toml/parse.go | 40 ++++++++++++++++++++++ utils/toml/unmarshal.go | 11 ++++++ 10 files changed, 325 insertions(+) create mode 100644 utils/collections/types.go create mode 100644 utils/errors/errors.go create mode 100644 utils/logger/constants.go create mode 100644 utils/logger/functions.go create mode 100644 utils/logger/logger.go create mode 100644 utils/logger/types.go create mode 100644 utils/toml/defaults.go create mode 100644 utils/toml/load.go create mode 100644 utils/toml/parse.go create mode 100644 utils/toml/unmarshal.go (limited to 'utils') diff --git a/utils/collections/types.go b/utils/collections/types.go new file mode 100644 index 0000000..c75d1ea --- /dev/null +++ b/utils/collections/types.go @@ -0,0 +1,3 @@ +package collections + +type Record map[string]any diff --git a/utils/errors/errors.go b/utils/errors/errors.go new file mode 100644 index 0000000..015356a --- /dev/null +++ b/utils/errors/errors.go @@ -0,0 +1,14 @@ +package errors + +import ( + "errors" + "fmt" +) + +func Error(message string, arguments ...any) error { + if len(arguments) == 0 { + return errors.New(message) + } + + return fmt.Errorf(message, arguments...) +} diff --git a/utils/logger/constants.go b/utils/logger/constants.go new file mode 100644 index 0000000..aba37ae --- /dev/null +++ b/utils/logger/constants.go @@ -0,0 +1,25 @@ +package logger + +const ( + ANSI_RESET = "\033[0m" +) + +const ( + LEVEL_COLOR_DEBUG = "\033[35mDEBUG \033[0m" + LEVEL_COLOR_ERROR = "\033[31mERROR \033[0m" + LEVEL_COLOR_INFO = "\033[34mINFO \033[0m" + LEVEL_COLOR_WARN = "\033[33mWARN \033[0m" +) + +const ( + MESSAGE_COLOR_DEBUG = "\033[90m" + MESSAGE_COLOR_ERROR = "\033[31m" + MESSAGE_COLOR_INFO = "\033[97m" + MESSAGE_COLOR_SUCCESS = "\033[32m" + MESSAGE_COLOR_WARN = "\033[33m" +) + +const ( + PREFIX_COLOR = "\033[36m" + PREFIX_WIDTH = 15 +) diff --git a/utils/logger/functions.go b/utils/logger/functions.go new file mode 100644 index 0000000..7bd9850 --- /dev/null +++ b/utils/logger/functions.go @@ -0,0 +1,53 @@ +package logger + +import ( + "fmt" + "strings" + + "go.uber.org/zap/zapcore" +) + +func formatLevel(level zapcore.Level, encoder zapcore.PrimitiveArrayEncoder) { + switch level { + case zapcore.DebugLevel: + encoder.AppendString(LEVEL_COLOR_DEBUG) + case zapcore.WarnLevel: + encoder.AppendString(LEVEL_COLOR_WARN) + case zapcore.ErrorLevel: + encoder.AppendString(LEVEL_COLOR_ERROR) + default: + encoder.AppendString(LEVEL_COLOR_INFO) + } +} + +func formatPrefix(prefix string) string { + if prefix == "" { + return "" + } + + padding := "" + if len(prefix) < PREFIX_WIDTH { + padding = strings.Repeat(" ", PREFIX_WIDTH-len(prefix)) + } + + return PREFIX_COLOR + "[" + prefix + "]" + ANSI_RESET + padding +} + +func colorizeMessage(level logLevel, message string) string { + switch level { + case levelDebug: + return MESSAGE_COLOR_DEBUG + message + ANSI_RESET + case levelWarn: + return MESSAGE_COLOR_WARN + message + ANSI_RESET + case levelError: + return MESSAGE_COLOR_ERROR + message + ANSI_RESET + case levelSuccess: + return MESSAGE_COLOR_SUCCESS + message + ANSI_RESET + default: + return MESSAGE_COLOR_INFO + message + ANSI_RESET + } +} + +func buildFullMessage(level logLevel, prefix string, message any) string { + return formatPrefix(prefix) + colorizeMessage(level, fmt.Sprint(message)) +} diff --git a/utils/logger/logger.go b/utils/logger/logger.go new file mode 100644 index 0000000..b1d1809 --- /dev/null +++ b/utils/logger/logger.go @@ -0,0 +1,83 @@ +package logger + +import ( + "fmt" + "os" + + "dove/messages" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var ( + instance *zap.Logger + atomicLevel zap.AtomicLevel +) + +func Init() { + atomicLevel = zap.NewAtomicLevelAt(zapcore.InfoLevel) + + encoderConfig := zapcore.EncoderConfig{ + LevelKey: "level", + MessageKey: "msg", + LineEnding: "\n", + EncodeLevel: formatLevel, + } + + encoder := zapcore.NewConsoleEncoder(encoderConfig) + stdoutSink := zapcore.AddSync(os.Stdout) + stderrSink := zapcore.AddSync(os.Stderr) + + core := zapcore.NewTee( + zapcore.NewCore(encoder, stdoutSink, zap.LevelEnablerFunc(func(level zapcore.Level) bool { + return level < zapcore.WarnLevel && atomicLevel.Enabled(level) + })), + zapcore.NewCore(encoder, stderrSink, zap.LevelEnablerFunc(func(level zapcore.Level) bool { + return level >= zapcore.WarnLevel && atomicLevel.Enabled(level) + })), + ) + + instance = zap.New(core, zap.AddCaller()) +} + +func SetDebug(enabled bool) { + if enabled { + atomicLevel.SetLevel(zapcore.DebugLevel) + } else { + atomicLevel.SetLevel(zapcore.InfoLevel) + } +} + +func Debugf(prefix string, format string, arguments ...any) { + emit(levelDebug, zapcore.DebugLevel, prefix, fmt.Sprintf(format, arguments...)) +} + +func Infof(prefix string, format string, arguments ...any) { + emit(levelInfo, zapcore.InfoLevel, prefix, fmt.Sprintf(format, arguments...)) +} + +func Successf(prefix string, format string, arguments ...any) { + emit(levelSuccess, zapcore.InfoLevel, prefix, fmt.Sprintf(format, arguments...)) +} + +func Warnf(prefix string, format string, arguments ...any) { + emit(levelWarn, zapcore.WarnLevel, prefix, fmt.Sprintf(format, arguments...)) +} + +func Errorf(prefix string, format string, arguments ...any) { + emit(levelError, zapcore.ErrorLevel, prefix, fmt.Sprintf(format, arguments...)) +} + +func Fatalf(prefix string, format string, arguments ...any) { + emit(levelError, zapcore.ErrorLevel, prefix, fmt.Sprintf(format, arguments...)) + os.Exit(1) +} + +func emit(levelLabel logLevel, zapLevel zapcore.Level, prefix string, message any) { + if instance == nil { + panic(messages.LoggerNotInitialized) + } + + instance.Log(zapLevel, buildFullMessage(levelLabel, prefix, message)) +} diff --git a/utils/logger/types.go b/utils/logger/types.go new file mode 100644 index 0000000..fce392d --- /dev/null +++ b/utils/logger/types.go @@ -0,0 +1,11 @@ +package logger + +type logLevel string + +const ( + levelDebug logLevel = "debug" + levelInfo logLevel = "info" + levelWarn logLevel = "warn" + levelError logLevel = "error" + levelSuccess logLevel = "success" +) diff --git a/utils/toml/defaults.go b/utils/toml/defaults.go new file mode 100644 index 0000000..e9ef032 --- /dev/null +++ b/utils/toml/defaults.go @@ -0,0 +1,64 @@ +package toml + +import ( + "reflect" + "strconv" +) + +func ApplyDefaults(target any) { + targetValue := reflect.ValueOf(target) + + if targetValue.Kind() != reflect.Pointer || targetValue.Elem().Kind() != reflect.Struct { + return + } + + applyStructDefaults(targetValue.Elem()) +} + +func applyStructDefaults(structValue reflect.Value) { + structType := structValue.Type() + + for fieldIndex := range structType.NumField() { + fieldValue := structValue.Field(fieldIndex) + fieldDescriptor := structType.Field(fieldIndex) + + if !fieldValue.CanSet() { + continue + } + + if fieldValue.Kind() == reflect.Struct { + applyStructDefaults(fieldValue) + continue + } + + defaultValue := fieldDescriptor.Tag.Get("default") + if defaultValue == "" { + continue + } + + setDefaultValue(fieldValue, defaultValue) + } +} + +func setDefaultValue(field reflect.Value, defaultValue string) { + if !isZeroValue(field) { + return + } + + switch field.Kind() { + case reflect.String: + field.SetString(defaultValue) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if parsed, parseError := strconv.ParseInt(defaultValue, 10, 64); parseError == nil { + field.SetInt(parsed) + } + case reflect.Bool: + if parsed, parseError := strconv.ParseBool(defaultValue); parseError == nil { + field.SetBool(parsed) + } + } +} + +func isZeroValue(field reflect.Value) bool { + return field.IsZero() +} diff --git a/utils/toml/load.go b/utils/toml/load.go new file mode 100644 index 0000000..a0cab4b --- /dev/null +++ b/utils/toml/load.go @@ -0,0 +1,21 @@ +package toml + +import ( + "os" + + "dove/messages" + "dove/utils/collections" + "dove/utils/errors" +) + +var loadedData collections.Record + +func LoadFile(filePath string) error { + fileContent, readError := os.ReadFile(filePath) + if readError != nil { + return errors.Error(messages.ConfigFileReadFailed, filePath, readError.Error()) + } + + loadedData = make(collections.Record) + return unmarshalContent(fileContent, &loadedData) +} diff --git a/utils/toml/parse.go b/utils/toml/parse.go new file mode 100644 index 0000000..7e2e6f2 --- /dev/null +++ b/utils/toml/parse.go @@ -0,0 +1,40 @@ +package toml + +import ( + "reflect" + "strings" + + "dove/messages" + "dove/utils/errors" +) + +func Parse(target any) error { + targetValue := reflect.ValueOf(target) + if targetValue.Kind() != reflect.Pointer || targetValue.Elem().Kind() != reflect.Struct { + return errors.Error(messages.ParseTargetMustBeStructPointer) + } + + ApplyDefaults(target) + + if loadedData == nil { + return nil + } + + sectionName := resolveSectionName(targetValue) + sectionData, exists := loadedData[sectionName] + if !exists { + return nil + } + + sectionBytes, marshalError := marshalSection(sectionData) + if marshalError != nil { + return errors.Error(messages.ConfigSectionInvalid, sectionName) + } + + return unmarshalContent(sectionBytes, target) +} + +func resolveSectionName(targetValue reflect.Value) string { + typeName := targetValue.Elem().Type().Name() + return strings.ToLower(typeName) +} diff --git a/utils/toml/unmarshal.go b/utils/toml/unmarshal.go new file mode 100644 index 0000000..bc4b6f4 --- /dev/null +++ b/utils/toml/unmarshal.go @@ -0,0 +1,11 @@ +package toml + +import "github.com/pelletier/go-toml/v2" + +func unmarshalContent(data []byte, target any) error { + return toml.Unmarshal(data, target) +} + +func marshalSection(sectionData any) ([]byte, error) { + return toml.Marshal(sectionData) +} -- cgit v1.2.3