From 09ef19bc9ef1dcd74002c26ce09747f395ddcc6ea2ba24eaeedc5b684bc64d29 Mon Sep 17 00:00:00 2001 From: Lixen Wraith Date: Wed, 16 Jul 2025 03:27:41 -0400 Subject: [PATCH] e1.11.0 Configuration refactored and simplified (interface changed). --- compat/builder.go | 163 +++++++++++++++++++++++++++++++------------ config.go | 174 +++------------------------------------------- format.go | 22 +++--- go.mod | 4 +- go.sum | 8 +-- logger.go | 35 ++++------ processor.go | 20 ++++-- state.go | 93 ------------------------- storage.go | 2 +- utility.go | 131 ---------------------------------- 10 files changed, 170 insertions(+), 482 deletions(-) diff --git a/compat/builder.go b/compat/builder.go index 0145a61..253a172 100644 --- a/compat/builder.go +++ b/compat/builder.go @@ -2,71 +2,144 @@ package compat import ( + "fmt" "github.com/lixenwraith/log" - "github.com/panjf2000/gnet/v2" - "github.com/valyala/fasthttp" ) -// Builder provides a convenient way to create configured loggers for both frameworks +// Builder provides a flexible way to create configured logger adapters for gnet and fasthttp. +// It can use an existing *log.Logger instance or create a new one from a *log.Config. type Builder struct { - logger *log.Logger - options []string // InitWithDefaults options + logger *log.Logger + logCfg *log.Config + err error } -// NewBuilder creates a new adapter builder +// NewBuilder creates a new adapter builder. func NewBuilder() *Builder { - return &Builder{ - logger: log.NewLogger(), - } + return &Builder{} } -// WithOptions adds configuration options for the underlying logger -func (b *Builder) WithOptions(opts ...string) *Builder { - b.options = append(b.options, opts...) +// WithLogger specifies an existing logger to use for the adapters. This is the recommended +// approach for applications that already have a central logger instance. +// If this is set, any configuration passed via WithConfig is ignored. +func (b *Builder) WithLogger(l *log.Logger) *Builder { + if l == nil { + b.err = fmt.Errorf("log/compat: provided logger cannot be nil") + return b + } + b.logger = l return b } -// Build initializes the logger and returns adapters for both frameworks -func (b *Builder) Build() (*GnetAdapter, *FastHTTPAdapter, error) { - // Initialize the logger - if err := b.logger.InitWithDefaults(b.options...); err != nil { - return nil, nil, err +// WithConfig provides a configuration for a new logger instance. +// This is used only if an existing logger is NOT provided via WithLogger. +// If neither WithLogger nor WithConfig is used, a default logger will be created. +func (b *Builder) WithConfig(cfg *log.Config) *Builder { + b.logCfg = cfg + return b +} + +// getLogger resolves the logger to be used, creating one if necessary. +// It's called internally by the build methods. +func (b *Builder) getLogger() (*log.Logger, error) { + if b.err != nil { + return nil, b.err } - // Create adapters - gnetAdapter := NewGnetAdapter(b.logger) - fasthttpAdapter := NewFastHTTPAdapter(b.logger) - - return gnetAdapter, fasthttpAdapter, nil -} - -// BuildStructured initializes the logger and returns structured adapters -func (b *Builder) BuildStructured() (*StructuredGnetAdapter, *FastHTTPAdapter, error) { - // Initialize the logger - if err := b.logger.InitWithDefaults(b.options...); err != nil { - return nil, nil, err + // An existing logger was provided, so we use it. + if b.logger != nil { + return b.logger, nil } - // Create adapters - gnetAdapter := NewStructuredGnetAdapter(b.logger) - fasthttpAdapter := NewFastHTTPAdapter(b.logger) + // Create a new logger instance. + l := log.NewLogger() + cfg := b.logCfg + if cfg == nil { + // If no config was provided, use the default. + cfg = log.DefaultConfig() + } - return gnetAdapter, fasthttpAdapter, nil + // Apply the configuration. + if err := l.ApplyConfig(cfg); err != nil { + return nil, err + } + + // Cache the newly created logger for subsequent builds with this builder. + b.logger = l + return l, nil } -// GetLogger returns the underlying logger for direct access -func (b *Builder) GetLogger() *log.Logger { - return b.logger +// BuildGnet creates a gnet adapter. +// It can be used for servers that require a standard gnet logger. +func (b *Builder) BuildGnet(opts ...GnetOption) (*GnetAdapter, error) { + l, err := b.getLogger() + if err != nil { + return nil, err + } + return NewGnetAdapter(l, opts...), nil } -// Example usage functions - -// ConfigureGnetServer configures a gnet server with the logger -func ConfigureGnetServer(adapter *GnetAdapter, opts ...gnet.Option) []gnet.Option { - return append(opts, gnet.WithLogger(adapter)) +// BuildStructuredGnet creates a gnet adapter that attempts to extract structured +// fields from log messages for richer, queryable logs. +func (b *Builder) BuildStructuredGnet(opts ...GnetOption) (*StructuredGnetAdapter, error) { + l, err := b.getLogger() + if err != nil { + return nil, err + } + return NewStructuredGnetAdapter(l, opts...), nil } -// ConfigureFastHTTPServer configures a fasthttp server with the logger -func ConfigureFastHTTPServer(adapter *FastHTTPAdapter, server *fasthttp.Server) { - server.Logger = adapter -} \ No newline at end of file +// BuildFastHTTP creates a fasthttp adapter. +func (b *Builder) BuildFastHTTP(opts ...FastHTTPOption) (*FastHTTPAdapter, error) { + l, err := b.getLogger() + if err != nil { + return nil, err + } + return NewFastHTTPAdapter(l, opts...), nil +} + +// GetLogger returns the underlying *log.Logger instance. +// If a logger has not been provided or created yet, it will be initialized. +func (b *Builder) GetLogger() (*log.Logger, error) { + return b.getLogger() +} + +// --- Example Usage --- +// +// The following demonstrates how to integrate lixenwraith/log with gnet and fasthttp +// using a single, shared logger instance. +// +// // 1. Create and configure your application's main logger. +// appLogger := log.NewLogger() +// logCfg := log.DefaultConfig() +// logCfg.Level = log.LevelDebug +// if err := appLogger.ApplyConfig(logCfg); err != nil { +// panic(fmt.Sprintf("failed to configure logger: %v", err)) +// } +// +// // 2. Create a builder and provide the existing logger. +// builder := compat.NewBuilder().WithLogger(appLogger) +// +// // 3. Build the required adapters. +// gnetLogger, err := builder.BuildGnet() +// if err != nil { /* handle error */ } +// +// fasthttpLogger, err := builder.BuildFastHTTP() +// if err != nil { /* handle error */ } +// +// // 4. Configure your servers with the adapters. +// +// // For gnet: +// var events gnet.EventHandler // your-event-handler +// // The adapter is passed directly into the gnet options. +// go gnet.Run(events, "tcp://:9000", gnet.WithLogger(gnetLogger)) +// +// // For fasthttp: +// // The adapter is assigned directly to the server's Logger field. +// server := &fasthttp.Server{ +// Handler: func(ctx *fasthttp.RequestCtx) { +// ctx.WriteString("Hello, world!") +// }, +// Logger: fasthttpLogger, +// } +// go server.ListenAndServe(":8080") \ No newline at end of file diff --git a/config.go b/config.go index da36e0b..0dd2851 100644 --- a/config.go +++ b/config.go @@ -2,10 +2,6 @@ package log import ( - "errors" - "fmt" - "github.com/lixenwraith/config" - "reflect" "strings" "time" ) @@ -105,161 +101,17 @@ var defaultConfig = Config{ // DefaultConfig returns a copy of the default configuration func DefaultConfig() *Config { // Create a copy to prevent modifications to the original - copiedConfig := defaultConfig + return defaultConfig.Clone() +} + +// Clone creates a deep copy of the configuration +func (c *Config) Clone() *Config { + copiedConfig := *c return &copiedConfig } -// NewConfigFromFile loads configuration from a TOML file and returns a validated Config -func NewConfigFromFile(path string) (*Config, error) { - cfg := DefaultConfig() - - // Use lixenwraith/config as a loader - loader := config.New() - - // Register the struct to enable proper unmarshaling - if err := loader.RegisterStruct("log.", *cfg); err != nil { - return nil, fmt.Errorf("failed to register config struct: %w", err) - } - - // Load from file (handles file not found gracefully) - if err := loader.Load(path, nil); err != nil && !errors.Is(err, config.ErrConfigNotFound) { - return nil, fmt.Errorf("failed to load config from %s: %w", path, err) - } - - // Extract values into our Config struct - if err := extractConfig(loader, "log.", cfg); err != nil { - return nil, fmt.Errorf("failed to extract config values: %w", err) - } - - // Validate the loaded configuration - if err := cfg.validate(); err != nil { - return nil, err - } - - return cfg, nil -} - -// NewConfigFromDefaults creates a Config with default values and applies overrides -func NewConfigFromDefaults(overrides map[string]any) (*Config, error) { - cfg := DefaultConfig() - - // Apply overrides using reflection - if err := applyOverrides(cfg, overrides); err != nil { - return nil, fmt.Errorf("failed to apply overrides: %w", err) - } - - // Validate the configuration - if err := cfg.validate(); err != nil { - return nil, err - } - - return cfg, nil -} - -// extractConfig extracts values from lixenwraith/config into our Config struct -func extractConfig(loader *config.Config, prefix string, cfg *Config) error { - v := reflect.ValueOf(cfg).Elem() - t := v.Type() - - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - fieldValue := v.Field(i) - - // Get the toml tag to determine the config key - tomlTag := field.Tag.Get("toml") - if tomlTag == "" { - continue - } - - key := prefix + tomlTag - - // Get value from loader - val, found := loader.Get(key) - if !found { - continue // Use default value - } - - // Set the field value with type conversion - if err := setFieldValue(fieldValue, val); err != nil { - return fmt.Errorf("failed to set field %s: %w", field.Name, err) - } - } - - return nil -} - -// applyOverrides applies a map of overrides to the Config struct -func applyOverrides(cfg *Config, overrides map[string]any) error { - v := reflect.ValueOf(cfg).Elem() - t := v.Type() - - // Create a map of field names to field values for efficient lookup - fieldMap := make(map[string]reflect.Value) - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - tomlTag := field.Tag.Get("toml") - if tomlTag != "" { - fieldMap[tomlTag] = v.Field(i) - } - } - - for key, value := range overrides { - fieldValue, exists := fieldMap[key] - if !exists { - return fmt.Errorf("unknown config key: %s", key) - } - - if err := setFieldValue(fieldValue, value); err != nil { - return fmt.Errorf("failed to set %s: %w", key, err) - } - } - - return nil -} - -// setFieldValue sets a reflect.Value with proper type conversion -func setFieldValue(field reflect.Value, value any) error { - switch field.Kind() { - case reflect.String: - strVal, ok := value.(string) - if !ok { - return fmt.Errorf("expected string, got %T", value) - } - field.SetString(strVal) - - case reflect.Int64: - switch v := value.(type) { - case int64: - field.SetInt(v) - case int: - field.SetInt(int64(v)) - default: - return fmt.Errorf("expected int64, got %T", value) - } - - case reflect.Float64: - floatVal, ok := value.(float64) - if !ok { - return fmt.Errorf("expected float64, got %T", value) - } - field.SetFloat(floatVal) - - case reflect.Bool: - boolVal, ok := value.(bool) - if !ok { - return fmt.Errorf("expected bool, got %T", value) - } - field.SetBool(boolVal) - - default: - return fmt.Errorf("unsupported field type: %v", field.Kind()) - } - - return nil -} - -// validate performs validation on the configuration -func (c *Config) validate() error { +// Validate performs validation on the configuration +func (c *Config) Validate() error { // String validations if strings.TrimSpace(c.Name) == "" { return fmtErrorf("log name cannot be empty") @@ -277,8 +129,8 @@ func (c *Config) validate() error { return fmtErrorf("timestamp_format cannot be empty") } - if c.StdoutTarget != "stdout" && c.StdoutTarget != "stderr" { - return fmtErrorf("invalid stdout_target: '%s' (use stdout or stderr)", c.StdoutTarget) + if c.StdoutTarget != "stdout" && c.StdoutTarget != "stderr" && c.StdoutTarget != "split" { + return fmtErrorf("invalid stdout_target: '%s' (use stdout, stderr, or split)", c.StdoutTarget) } // Numeric validations @@ -319,10 +171,4 @@ func (c *Config) validate() error { } return nil -} - -// Clone creates a deep copy of the configuration -func (c *Config) Clone() *Config { - copiedConfig := *c - return &copiedConfig } \ No newline at end of file diff --git a/format.go b/format.go index 15c543f..f3dd46c 100644 --- a/format.go +++ b/format.go @@ -364,24 +364,18 @@ func (s *serializer) serializeStructuredJSON(flags int64, timestamp time.Time, l s.buf = append(s.buf, `"message":"`...) s.writeString(message) s.buf = append(s.buf, '"') - needsComma = true - // // Add trace if present - // if trace != "" { - // if needsComma { - // s.buf = append(s.buf, ',') - // } - // s.buf = append(s.buf, `"trace":"`...) - // s.writeString(trace) - // s.buf = append(s.buf, '"') - // needsComma = true - // } + // Add trace if present + if trace != "" { + s.buf = append(s.buf, ',') + s.buf = append(s.buf, `"trace":"`...) + s.writeString(trace) + s.buf = append(s.buf, '"') + } // Marshal fields using encoding/json if len(fields) > 0 { - if needsComma { - s.buf = append(s.buf, ',') - } + s.buf = append(s.buf, ',') s.buf = append(s.buf, `"fields":`...) // Use json.Marshal for proper encoding diff --git a/go.mod b/go.mod index ea44a02..ff7d9b5 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,9 @@ go 1.24.5 require ( github.com/davecgh/go-spew v1.1.1 - github.com/lixenwraith/config v0.0.0-20250712170030-7d38402e0497 + github.com/lixenwraith/config v0.0.0-20250715165746-b26e47c0c757 github.com/panjf2000/gnet/v2 v2.9.1 - github.com/valyala/fasthttp v1.63.0 + github.com/valyala/fasthttp v1.64.0 ) require ( diff --git a/go.sum b/go.sum index 2d0a412..501ffa9 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= -github.com/lixenwraith/config v0.0.0-20250712170030-7d38402e0497 h1:ixTIdJSd945n/IhMRwGwQVmQnQ1nUr5z1wn31jXq9FU= -github.com/lixenwraith/config v0.0.0-20250712170030-7d38402e0497/go.mod h1:y7kgDrWIFROWJJ6ASM/SPTRRAj27FjRGWh2SDLcdQ68= +github.com/lixenwraith/config v0.0.0-20250715165746-b26e47c0c757 h1:VTopw1oA7XijJa+5ZTneVLZGD4LPmUHITdqaCckfI78= +github.com/lixenwraith/config v0.0.0-20250715165746-b26e47c0c757/go.mod h1:y7kgDrWIFROWJJ6ASM/SPTRRAj27FjRGWh2SDLcdQ68= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg= @@ -20,8 +20,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.63.0 h1:DisIL8OjB7ul2d7cBaMRcKTQDYnrGy56R4FCiuDP0Ns= -github.com/valyala/fasthttp v1.63.0/go.mod h1:REc4IeW+cAEyLrRPa5A81MIjvz0QE1laoTX2EaPHKJM= +github.com/valyala/fasthttp v1.64.0 h1:QBygLLQmiAyiXuRhthf0tuRkqAFcrC42dckN2S+N3og= +github.com/valyala/fasthttp v1.64.0/go.mod h1:dGmFxwkWXSK0NbOSJuF7AMVzU+lkHz0wQVvVITv2UQA= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= diff --git a/logger.go b/logger.go index 7710a9b..aadacd6 100644 --- a/logger.go +++ b/logger.go @@ -9,8 +9,6 @@ import ( "sync" "sync/atomic" "time" - - "github.com/lixenwraith/config" ) // Logger is the core struct that encapsulates all logger functionality @@ -57,16 +55,16 @@ func NewLogger() *Logger { return l } -// getConfig returns the current configuration (thread-safe) -func (l *Logger) getConfig() *Config { - return l.currentConfig.Load().(*Config) -} +// ApplyConfig applies a validated configuration to the logger +// This is the primary way applications should configure the logger +func (l *Logger) ApplyConfig(cfg *Config) error { + if cfg == nil { + return fmt.Errorf("log: configuration cannot be nil") + } -// LoadConfig loads logger configuration from a file -func (l *Logger) LoadConfig(path string) error { - cfg, err := NewConfigFromFile(path) - if err != nil { - return err + // Validate the configuration + if err := cfg.Validate(); err != nil { + return fmt.Errorf("log: invalid configuration: %w", err) } l.initMu.Lock() @@ -75,18 +73,9 @@ func (l *Logger) LoadConfig(path string) error { return l.apply(cfg) } -// SaveConfig saves the current logger configuration to a file -func (l *Logger) SaveConfig(path string) error { - // Create a lixenwraith/config instance for saving - saver := config.New() - cfg := l.getConfig() - - // Register all fields with their current values - if err := saver.RegisterStruct("log.", *cfg); err != nil { - return fmt.Errorf("failed to register config for saving: %w", err) - } - - return saver.Save(path) +// getConfig returns the current configuration (thread-safe) +func (l *Logger) getConfig() *Config { + return l.currentConfig.Load().(*Config) } // apply applies a validated configuration and reconfigures logger components diff --git a/processor.go b/processor.go index b5351bf..73e8e8d 100644 --- a/processor.go +++ b/processor.go @@ -15,7 +15,7 @@ const ( adaptiveIntervalFactor float64 = 1.5 // Slow down adaptiveSpeedUpFactor float64 = 0.8 // Speed up // Minimum wait time used throughout the package - minWaitTime = time.Duration(10 * time.Millisecond) + minWaitTime = 10 * time.Millisecond ) // processLogs is the main log processing loop running in a separate goroutine @@ -50,7 +50,7 @@ func (l *Logger) processLogs(ch <-chan logRecord) { // State variables for adaptive disk checks var bytesSinceLastCheck int64 = 0 - var lastCheckTime time.Time = time.Now() + var lastCheckTime = time.Now() var logsSinceLastCheck int64 = 0 // --- Main Loop --- @@ -235,10 +235,20 @@ func (l *Logger) processLogRecord(record logRecord) int64 { enableStdout := c.EnableStdout if enableStdout { if s := l.state.StdoutWriter.Load(); s != nil { - // Assert to concrete type: *sink if sinkWrapper, ok := s.(*sink); ok && sinkWrapper != nil { - // Use the wrapped writer (sinkWrapper.w) - _, _ = sinkWrapper.w.Write(data) + // Handle split mode + if c.StdoutTarget == "split" { + if record.Level >= LevelWarn { + // Write WARN and ERROR to stderr + _, _ = os.Stderr.Write(data) + } else { + // Write INFO and DEBUG to stdout + _, _ = sinkWrapper.w.Write(data) + } + } else { + // Write to the configured target (stdout or stderr) + _, _ = sinkWrapper.w.Write(data) + } } } } diff --git a/state.go b/state.go index 9948c1d..3b44999 100644 --- a/state.go +++ b/state.go @@ -2,12 +2,8 @@ package log import ( - "fmt" "io" "os" - "reflect" - "strconv" - "strings" "sync" "sync/atomic" "time" @@ -46,95 +42,6 @@ type sink struct { w io.Writer } -// Init initializes the logger using a map of configuration values -func (l *Logger) Init(values map[string]any) error { - cfg, err := NewConfigFromDefaults(values) - if err != nil { - l.state.LoggerDisabled.Store(true) - return err - } - - l.initMu.Lock() - defer l.initMu.Unlock() - - if l.state.LoggerDisabled.Load() { - return fmtErrorf("logger previously failed to initialize and is disabled") - } - - return l.apply(cfg) -} - -// InitWithDefaults initializes the logger with built-in defaults and optional overrides -func (l *Logger) InitWithDefaults(overrides ...string) error { - // Parse overrides into a map - overrideMap := make(map[string]any) - - defaults := DefaultConfig() - for _, override := range overrides { - key, valueStr, err := parseKeyValue(override) - if err != nil { - return err - } - - fieldType, err := getFieldType(defaults, key) - if err != nil { - return fmtErrorf("unknown config key: %s", key) - } - - // Parse the value based on the field type - var parsedValue any - switch fieldType { - case "int64": - parsedValue, err = strconv.ParseInt(valueStr, 10, 64) - case "string": - parsedValue = valueStr - case "bool": - parsedValue, err = strconv.ParseBool(valueStr) - case "float64": - parsedValue, err = strconv.ParseFloat(valueStr, 64) - default: - return fmtErrorf("unsupported type for key '%s': %s", key, fieldType) - } - - if err != nil { - return fmtErrorf("invalid value format for '%s': %w", key, err) - } - - overrideMap[strings.ToLower(key)] = parsedValue - } - - return l.Init(overrideMap) -} - -// getFieldType uses reflection to determine the type of a config field -func getFieldType(cfg *Config, fieldName string) (string, error) { - v := reflect.ValueOf(cfg).Elem() - t := v.Type() - - fieldName = strings.ToLower(fieldName) - - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - tomlTag := field.Tag.Get("toml") - if strings.ToLower(tomlTag) == fieldName { - switch field.Type.Kind() { - case reflect.String: - return "string", nil - case reflect.Int64: - return "int64", nil - case reflect.Float64: - return "float64", nil - case reflect.Bool: - return "bool", nil - default: - return "", fmt.Errorf("unsupported field type: %v", field.Type.Kind()) - } - } - } - - return "", fmt.Errorf("field not found") -} - // Shutdown gracefully closes the logger, attempting to flush pending records // If no timeout is provided, uses a default of 2x flush interval func (l *Logger) Shutdown(timeout ...time.Duration) error { diff --git a/storage.go b/storage.go index ec1bc92..843eab4 100644 --- a/storage.go +++ b/storage.go @@ -153,7 +153,7 @@ func (l *Logger) getDiskFreeSpace(path string) (int64, error) { if err := syscall.Statfs(path, &stat); err != nil { return 0, fmtErrorf("failed to get disk stats for '%s': %w", path, err) } - availableBytes := int64(stat.Bavail) * int64(stat.Bsize) + availableBytes := int64(stat.Bavail) * stat.Bsize return availableBytes, nil } diff --git a/utility.go b/utility.go index d93f2b4..a9cf0f4 100644 --- a/utility.go +++ b/utility.go @@ -112,135 +112,4 @@ func Level(levelStr string) (int64, error) { default: return 0, fmtErrorf("invalid level string: '%s' (use debug, info, warn, error, proc, disk, sys)", levelStr) } -} - -// validateConfigValue validates a single configuration field -func validateConfigValue(key string, value any) error { - keyLower := strings.ToLower(key) - switch keyLower { - case "name": - v, ok := value.(string) - if !ok { - return fmtErrorf("name must be string, got %T", value) - } - if strings.TrimSpace(v) == "" { - return fmtErrorf("log name cannot be empty") - } - - case "format": - v, ok := value.(string) - if !ok { - return fmtErrorf("format must be string, got %T", value) - } - if v != "txt" && v != "json" && v != "raw" { - return fmtErrorf("invalid format: '%s' (use txt, json, or raw)", v) - } - - case "extension": - v, ok := value.(string) - if !ok { - return fmtErrorf("extension must be string, got %T", value) - } - if strings.HasPrefix(v, ".") { - return fmtErrorf("extension should not start with dot: %s", v) - } - - case "timestamp_format": - v, ok := value.(string) - if !ok { - return fmtErrorf("timestamp_format must be string, got %T", value) - } - if strings.TrimSpace(v) == "" { - return fmtErrorf("timestamp_format cannot be empty") - } - - case "buffer_size": - v, ok := value.(int64) - if !ok { - return fmtErrorf("buffer_size must be int64, got %T", value) - } - if v <= 0 { - return fmtErrorf("buffer_size must be positive: %d", v) - } - - case "max_size_mb", "max_total_size_mb", "min_disk_free_mb": - v, ok := value.(int64) - if !ok { - return fmtErrorf("%s must be int64, got %T", key, value) - } - if v < 0 { - return fmtErrorf("%s cannot be negative: %d", key, v) - } - - case "flush_interval_ms", "disk_check_interval_ms", "min_check_interval_ms", "max_check_interval_ms": - v, ok := value.(int64) - if !ok { - return fmtErrorf("%s must be int64, got %T", key, value) - } - if v <= 0 { - return fmtErrorf("%s must be positive milliseconds: %d", key, v) - } - - case "trace_depth": - v, ok := value.(int64) - if !ok { - return fmtErrorf("trace_depth must be int64, got %T", value) - } - if v < 0 || v > 10 { - return fmtErrorf("trace_depth must be between 0 and 10: %d", v) - } - - case "retention_period_hrs", "retention_check_mins": - v, ok := value.(float64) - if !ok { - return fmtErrorf("%s must be float64, got %T", key, value) - } - if v < 0 { - return fmtErrorf("%s cannot be negative: %f", key, v) - } - - case "heartbeat_level": - v, ok := value.(int64) - if !ok { - return fmtErrorf("heartbeat_level must be int64, got %T", value) - } - if v < 0 || v > 3 { - return fmtErrorf("heartbeat_level must be between 0 and 3: %d", v) - } - - case "heartbeat_interval_s": - _, ok := value.(int64) - if !ok { - return fmtErrorf("heartbeat_interval_s must be int64, got %T", value) - } - // Note: only validate positive if heartbeat is enabled (cross-field validation) - - case "stdout_target": - v, ok := value.(string) - if !ok { - return fmtErrorf("stdout_target must be string, got %T", value) - } - if v != "stdout" && v != "stderr" { - return fmtErrorf("invalid stdout_target: '%s' (use stdout or stderr)", v) - } - - case "level": - // Level validation if needed - _, ok := value.(int64) - if !ok { - return fmtErrorf("level must be int64, got %T", value) - } - - // Fields that don't need validation beyond type - case "directory", "show_timestamp", "show_level", "enable_adaptive_interval", - "enable_periodic_sync", "enable_stdout", "disable_file", "internal_errors_to_stderr": - // Type checking handled by config system - return nil - - default: - // Unknown field - let config system handle it - return nil - } - - return nil } \ No newline at end of file