e1.10.0 Configuration refactored.
This commit is contained in:
234
config.go
234
config.go
@ -2,6 +2,11 @@
|
|||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/lixenwraith/config"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -11,7 +16,7 @@ type Config struct {
|
|||||||
Level int64 `toml:"level"`
|
Level int64 `toml:"level"`
|
||||||
Name string `toml:"name"` // Base name for log files
|
Name string `toml:"name"` // Base name for log files
|
||||||
Directory string `toml:"directory"`
|
Directory string `toml:"directory"`
|
||||||
Format string `toml:"format"` // "txt" or "json"
|
Format string `toml:"format"` // "txt", "raw", or "json"
|
||||||
Extension string `toml:"extension"`
|
Extension string `toml:"extension"`
|
||||||
|
|
||||||
// Formatting
|
// Formatting
|
||||||
@ -100,39 +105,206 @@ var defaultConfig = Config{
|
|||||||
// DefaultConfig returns a copy of the default configuration
|
// DefaultConfig returns a copy of the default configuration
|
||||||
func DefaultConfig() *Config {
|
func DefaultConfig() *Config {
|
||||||
// Create a copy to prevent modifications to the original
|
// Create a copy to prevent modifications to the original
|
||||||
config := defaultConfig
|
copiedConfig := defaultConfig
|
||||||
return &config
|
return &copiedConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate performs basic sanity checks on the configuration values.
|
// 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 {
|
func (c *Config) validate() error {
|
||||||
// Individual field validations
|
// String validations
|
||||||
fields := map[string]any{
|
if strings.TrimSpace(c.Name) == "" {
|
||||||
"name": c.Name,
|
return fmtErrorf("log name cannot be empty")
|
||||||
"format": c.Format,
|
|
||||||
"extension": c.Extension,
|
|
||||||
"timestamp_format": c.TimestampFormat,
|
|
||||||
"buffer_size": c.BufferSize,
|
|
||||||
"max_size_mb": c.MaxSizeMB,
|
|
||||||
"max_total_size_mb": c.MaxTotalSizeMB,
|
|
||||||
"min_disk_free_mb": c.MinDiskFreeMB,
|
|
||||||
"flush_interval_ms": c.FlushIntervalMs,
|
|
||||||
"disk_check_interval_ms": c.DiskCheckIntervalMs,
|
|
||||||
"min_check_interval_ms": c.MinCheckIntervalMs,
|
|
||||||
"max_check_interval_ms": c.MaxCheckIntervalMs,
|
|
||||||
"trace_depth": c.TraceDepth,
|
|
||||||
"retention_period_hrs": c.RetentionPeriodHrs,
|
|
||||||
"retention_check_mins": c.RetentionCheckMins,
|
|
||||||
"heartbeat_level": c.HeartbeatLevel,
|
|
||||||
"heartbeat_interval_s": c.HeartbeatIntervalS,
|
|
||||||
"stdout_target": c.StdoutTarget,
|
|
||||||
"level": c.Level,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, value := range fields {
|
if c.Format != "txt" && c.Format != "json" && c.Format != "raw" {
|
||||||
if err := validateConfigValue(key, value); err != nil {
|
return fmtErrorf("invalid format: '%s' (use txt, json, or raw)", c.Format)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(c.Extension, ".") {
|
||||||
|
return fmtErrorf("extension should not start with dot: %s", c.Extension)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(c.TimestampFormat) == "" {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Numeric validations
|
||||||
|
if c.BufferSize <= 0 {
|
||||||
|
return fmtErrorf("buffer_size must be positive: %d", c.BufferSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.MaxSizeMB < 0 || c.MaxTotalSizeMB < 0 || c.MinDiskFreeMB < 0 {
|
||||||
|
return fmtErrorf("size limits cannot be negative")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.FlushIntervalMs <= 0 || c.DiskCheckIntervalMs <= 0 ||
|
||||||
|
c.MinCheckIntervalMs <= 0 || c.MaxCheckIntervalMs <= 0 {
|
||||||
|
return fmtErrorf("interval settings must be positive")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.TraceDepth < 0 || c.TraceDepth > 10 {
|
||||||
|
return fmtErrorf("trace_depth must be between 0 and 10: %d", c.TraceDepth)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.RetentionPeriodHrs < 0 || c.RetentionCheckMins < 0 {
|
||||||
|
return fmtErrorf("retention settings cannot be negative")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.HeartbeatLevel < 0 || c.HeartbeatLevel > 3 {
|
||||||
|
return fmtErrorf("heartbeat_level must be between 0 and 3: %d", c.HeartbeatLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cross-field validations
|
// Cross-field validations
|
||||||
@ -148,3 +320,9 @@ func (c *Config) validate() error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clone creates a deep copy of the configuration
|
||||||
|
func (c *Config) Clone() *Config {
|
||||||
|
copiedConfig := *c
|
||||||
|
return &copiedConfig
|
||||||
|
}
|
||||||
16
interface.go
16
interface.go
@ -44,29 +44,29 @@ type logRecord struct {
|
|||||||
// Debug logs a message at debug level.
|
// Debug logs a message at debug level.
|
||||||
func (l *Logger) Debug(args ...any) {
|
func (l *Logger) Debug(args ...any) {
|
||||||
flags := l.getFlags()
|
flags := l.getFlags()
|
||||||
traceDepth, _ := l.config.Int64("log.trace_depth")
|
cfg := l.getConfig()
|
||||||
l.log(flags, LevelDebug, traceDepth, args...)
|
l.log(flags, LevelDebug, cfg.TraceDepth, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info logs a message at info level.
|
// Info logs a message at info level.
|
||||||
func (l *Logger) Info(args ...any) {
|
func (l *Logger) Info(args ...any) {
|
||||||
flags := l.getFlags()
|
flags := l.getFlags()
|
||||||
traceDepth, _ := l.config.Int64("log.trace_depth")
|
cfg := l.getConfig()
|
||||||
l.log(flags, LevelInfo, traceDepth, args...)
|
l.log(flags, LevelInfo, cfg.TraceDepth, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warn logs a message at warning level.
|
// Warn logs a message at warning level.
|
||||||
func (l *Logger) Warn(args ...any) {
|
func (l *Logger) Warn(args ...any) {
|
||||||
flags := l.getFlags()
|
flags := l.getFlags()
|
||||||
traceDepth, _ := l.config.Int64("log.trace_depth")
|
cfg := l.getConfig()
|
||||||
l.log(flags, LevelWarn, traceDepth, args...)
|
l.log(flags, LevelWarn, cfg.TraceDepth, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error logs a message at error level.
|
// Error logs a message at error level.
|
||||||
func (l *Logger) Error(args ...any) {
|
func (l *Logger) Error(args ...any) {
|
||||||
flags := l.getFlags()
|
flags := l.getFlags()
|
||||||
traceDepth, _ := l.config.Int64("log.trace_depth")
|
cfg := l.getConfig()
|
||||||
l.log(flags, LevelError, traceDepth, args...)
|
l.log(flags, LevelError, cfg.TraceDepth, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DebugTrace logs a debug message with function call trace.
|
// DebugTrace logs a debug message with function call trace.
|
||||||
|
|||||||
216
logger.go
216
logger.go
@ -2,12 +2,12 @@
|
|||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lixenwraith/config"
|
"github.com/lixenwraith/config"
|
||||||
@ -15,7 +15,7 @@ import (
|
|||||||
|
|
||||||
// Logger is the core struct that encapsulates all logger functionality
|
// Logger is the core struct that encapsulates all logger functionality
|
||||||
type Logger struct {
|
type Logger struct {
|
||||||
config *config.Config
|
currentConfig atomic.Value // stores *Config
|
||||||
state State
|
state State
|
||||||
initMu sync.Mutex
|
initMu sync.Mutex
|
||||||
serializer *serializer
|
serializer *serializer
|
||||||
@ -24,12 +24,11 @@ type Logger struct {
|
|||||||
// NewLogger creates a new Logger instance with default settings
|
// NewLogger creates a new Logger instance with default settings
|
||||||
func NewLogger() *Logger {
|
func NewLogger() *Logger {
|
||||||
l := &Logger{
|
l := &Logger{
|
||||||
config: config.New(),
|
|
||||||
serializer: newSerializer(),
|
serializer: newSerializer(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register all configuration parameters with their defaults
|
// Set default configuration
|
||||||
l.registerConfigValues()
|
l.currentConfig.Store(DefaultConfig())
|
||||||
|
|
||||||
// Initialize the state
|
// Initialize the state
|
||||||
l.state.IsInitialized.Store(false)
|
l.state.IsInitialized.Store(false)
|
||||||
@ -58,130 +57,57 @@ func NewLogger() *Logger {
|
|||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadConfig loads logger configuration from a file with optional CLI overrides
|
// getConfig returns the current configuration (thread-safe)
|
||||||
func (l *Logger) LoadConfig(path string, args []string) error {
|
func (l *Logger) getConfig() *Config {
|
||||||
err := l.config.Load(path, args)
|
return l.currentConfig.Load().(*Config)
|
||||||
|
|
||||||
// Check if the error indicates that the file was not found
|
|
||||||
configExists := !errors.Is(err, config.ErrConfigNotFound)
|
|
||||||
|
|
||||||
// If there's an error other than "file not found", return it
|
|
||||||
if err != nil && !errors.Is(err, config.ErrConfigNotFound) {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no config file exists and no CLI args were provided, there's nothing to apply
|
// LoadConfig loads logger configuration from a file
|
||||||
if !configExists && len(args) == 0 {
|
func (l *Logger) LoadConfig(path string) error {
|
||||||
return nil
|
cfg, err := NewConfigFromFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
l.initMu.Lock()
|
l.initMu.Lock()
|
||||||
defer l.initMu.Unlock()
|
defer l.initMu.Unlock()
|
||||||
return l.applyConfig()
|
|
||||||
|
return l.apply(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveConfig saves the current logger configuration to a file
|
// SaveConfig saves the current logger configuration to a file
|
||||||
func (l *Logger) SaveConfig(path string) error {
|
func (l *Logger) SaveConfig(path string) error {
|
||||||
return l.config.Save(path)
|
// 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// registerConfigValues registers all configuration parameters with the config instance
|
return saver.Save(path)
|
||||||
func (l *Logger) registerConfigValues() {
|
|
||||||
// Register the entire config struct at once
|
|
||||||
err := l.config.RegisterStruct("log.", defaultConfig)
|
|
||||||
if err != nil {
|
|
||||||
l.internalLog("warning - failed to register config values: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateConfigFromExternal updates the logger config from an external config.Config instance
|
// apply applies a validated configuration and reconfigures logger components
|
||||||
func (l *Logger) updateConfigFromExternal(extCfg *config.Config, basePath string) error {
|
|
||||||
// Get our registered config paths (already registered during initialization)
|
|
||||||
registeredPaths := l.config.GetRegisteredPaths("log.")
|
|
||||||
if len(registeredPaths) == 0 {
|
|
||||||
// Register defaults first if not already done
|
|
||||||
l.registerConfigValues()
|
|
||||||
registeredPaths = l.config.GetRegisteredPaths("log.")
|
|
||||||
}
|
|
||||||
|
|
||||||
// For each registered path
|
|
||||||
for path := range registeredPaths {
|
|
||||||
// Extract local name and build external path
|
|
||||||
localName := strings.TrimPrefix(path, "log.")
|
|
||||||
fullPath := basePath + "." + localName
|
|
||||||
if basePath == "" {
|
|
||||||
fullPath = localName
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current value to use as default in external config
|
|
||||||
currentVal, found := l.config.Get(path)
|
|
||||||
if !found {
|
|
||||||
continue // Skip if not found (shouldn't happen)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register in external config with current value as default
|
|
||||||
err := extCfg.Register(fullPath, currentVal)
|
|
||||||
if err != nil {
|
|
||||||
return fmtErrorf("failed to register config key '%s': %w", fullPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get value from external config
|
|
||||||
val, found := extCfg.Get(fullPath)
|
|
||||||
if !found {
|
|
||||||
continue // Use existing value if not found in external config
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate and update
|
|
||||||
if err := validateConfigValue(localName, val); err != nil {
|
|
||||||
return fmtErrorf("invalid value for '%s': %w", localName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := l.config.Set(path, val); err != nil {
|
|
||||||
return fmtErrorf("failed to update config value for '%s': %w", path, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// applyConfig applies the configuration and reconfigures logger components
|
|
||||||
// Assumes initMu is held
|
// Assumes initMu is held
|
||||||
func (l *Logger) applyConfig() error {
|
func (l *Logger) apply(cfg *Config) error {
|
||||||
// Check parameter relationship issues
|
// Store the new configuration
|
||||||
minInterval, _ := l.config.Int64("log.min_check_interval_ms")
|
oldCfg := l.getConfig()
|
||||||
maxInterval, _ := l.config.Int64("log.max_check_interval_ms")
|
l.currentConfig.Store(cfg)
|
||||||
if minInterval > maxInterval {
|
|
||||||
l.internalLog("warning - min_check_interval_ms (%d) > max_check_interval_ms (%d), max will be used\n",
|
|
||||||
minInterval, maxInterval)
|
|
||||||
|
|
||||||
// Update min_check_interval_ms to equal max_check_interval_ms
|
// Update serializer format
|
||||||
err := l.config.Set("log.min_check_interval_ms", maxInterval)
|
l.serializer.setTimestampFormat(cfg.TimestampFormat)
|
||||||
if err != nil {
|
|
||||||
l.internalLog("warning - failed to update min_check_interval_ms: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate config (Basic)
|
|
||||||
currentCfg := l.loadCurrentConfig() // Helper to load struct from l.config
|
|
||||||
if err := currentCfg.validate(); err != nil {
|
|
||||||
l.state.LoggerDisabled.Store(true) // Disable logger on validation failure
|
|
||||||
return fmtErrorf("invalid configuration detected: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure log directory exists
|
// Ensure log directory exists
|
||||||
dir, _ := l.config.String("log.directory")
|
if err := os.MkdirAll(cfg.Directory, 0755); err != nil {
|
||||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
||||||
l.state.LoggerDisabled.Store(true)
|
l.state.LoggerDisabled.Store(true)
|
||||||
return fmtErrorf("failed to create log directory '%s': %w", dir, err)
|
l.currentConfig.Store(oldCfg) // Rollback
|
||||||
}
|
return fmtErrorf("failed to create log directory '%s': %w", cfg.Directory, err)
|
||||||
|
|
||||||
// Update serializer format when config changes
|
|
||||||
if tsFormat, err := l.config.String("log.timestamp_format"); err == nil && tsFormat != "" {
|
|
||||||
l.serializer.setTimestampFormat(tsFormat)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current state
|
// Get current state
|
||||||
wasInitialized := l.state.IsInitialized.Load()
|
wasInitialized := l.state.IsInitialized.Load()
|
||||||
disableFile, _ := l.config.Bool("log.disable_file")
|
|
||||||
|
|
||||||
// Get current file handle
|
// Get current file handle
|
||||||
currentFilePtr := l.state.CurrentFile.Load()
|
currentFilePtr := l.state.CurrentFile.Load()
|
||||||
@ -194,8 +120,8 @@ func (l *Logger) applyConfig() error {
|
|||||||
needsNewFile := !wasInitialized || currentFile == nil
|
needsNewFile := !wasInitialized || currentFile == nil
|
||||||
|
|
||||||
// Handle file state transitions
|
// Handle file state transitions
|
||||||
if disableFile {
|
if cfg.DisableFile {
|
||||||
// When disabling file output, properly close the current file
|
// When disabling file output, close the current file
|
||||||
if currentFile != nil {
|
if currentFile != nil {
|
||||||
// Sync and close the file
|
// Sync and close the file
|
||||||
_ = currentFile.Sync()
|
_ = currentFile.Sync()
|
||||||
@ -210,6 +136,7 @@ func (l *Logger) applyConfig() error {
|
|||||||
logFile, err := l.createNewLogFile()
|
logFile, err := l.createNewLogFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.state.LoggerDisabled.Store(true)
|
l.state.LoggerDisabled.Store(true)
|
||||||
|
l.currentConfig.Store(oldCfg) // Rollback
|
||||||
return fmtErrorf("failed to create log file: %w", err)
|
return fmtErrorf("failed to create log file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,8 +160,7 @@ func (l *Logger) applyConfig() error {
|
|||||||
oldCh := l.getCurrentLogChannel()
|
oldCh := l.getCurrentLogChannel()
|
||||||
if oldCh != nil {
|
if oldCh != nil {
|
||||||
// Create new channel then close old channel
|
// Create new channel then close old channel
|
||||||
bufferSize, _ := l.config.Int64("log.buffer_size")
|
newLogChannel := make(chan logRecord, cfg.BufferSize)
|
||||||
newLogChannel := make(chan logRecord, bufferSize)
|
|
||||||
l.state.ActiveLogChannel.Store(newLogChannel)
|
l.state.ActiveLogChannel.Store(newLogChannel)
|
||||||
close(oldCh)
|
close(oldCh)
|
||||||
|
|
||||||
@ -244,27 +170,23 @@ func (l *Logger) applyConfig() error {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Initial startup
|
// Initial startup
|
||||||
bufferSize, _ := l.config.Int64("log.buffer_size")
|
newLogChannel := make(chan logRecord, cfg.BufferSize)
|
||||||
newLogChannel := make(chan logRecord, bufferSize)
|
|
||||||
l.state.ActiveLogChannel.Store(newLogChannel)
|
l.state.ActiveLogChannel.Store(newLogChannel)
|
||||||
l.state.ProcessorExited.Store(false)
|
l.state.ProcessorExited.Store(false)
|
||||||
go l.processLogs(newLogChannel)
|
go l.processLogs(newLogChannel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup stdout writer based on config
|
// Setup stdout writer based on config
|
||||||
enableStdout, _ := l.config.Bool("log.enable_stdout")
|
if cfg.EnableStdout {
|
||||||
if enableStdout {
|
var writer io.Writer
|
||||||
target, _ := l.config.String("log.stdout_target")
|
if cfg.StdoutTarget == "stderr" {
|
||||||
if target == "stderr" {
|
writer = os.Stderr
|
||||||
var writer io.Writer = os.Stderr
|
|
||||||
l.state.StdoutWriter.Store(&sink{w: writer})
|
|
||||||
} else if target == "stdout" {
|
|
||||||
var writer io.Writer = os.Stdout
|
|
||||||
l.state.StdoutWriter.Store(&sink{w: writer})
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
var writer io.Writer = io.Discard
|
writer = os.Stdout
|
||||||
|
}
|
||||||
l.state.StdoutWriter.Store(&sink{w: writer})
|
l.state.StdoutWriter.Store(&sink{w: writer})
|
||||||
|
} else {
|
||||||
|
l.state.StdoutWriter.Store(&sink{w: io.Discard})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark as initialized
|
// Mark as initialized
|
||||||
@ -276,38 +198,6 @@ func (l *Logger) applyConfig() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadCurrentConfig loads the current configuration for validation
|
|
||||||
func (l *Logger) loadCurrentConfig() *Config {
|
|
||||||
cfg := &Config{}
|
|
||||||
cfg.Level, _ = l.config.Int64("log.level")
|
|
||||||
cfg.Name, _ = l.config.String("log.name")
|
|
||||||
cfg.Directory, _ = l.config.String("log.directory")
|
|
||||||
cfg.Format, _ = l.config.String("log.format")
|
|
||||||
cfg.Extension, _ = l.config.String("log.extension")
|
|
||||||
cfg.ShowTimestamp, _ = l.config.Bool("log.show_timestamp")
|
|
||||||
cfg.ShowLevel, _ = l.config.Bool("log.show_level")
|
|
||||||
cfg.TimestampFormat, _ = l.config.String("log.timestamp_format")
|
|
||||||
cfg.BufferSize, _ = l.config.Int64("log.buffer_size")
|
|
||||||
cfg.MaxSizeMB, _ = l.config.Int64("log.max_size_mb")
|
|
||||||
cfg.MaxTotalSizeMB, _ = l.config.Int64("log.max_total_size_mb")
|
|
||||||
cfg.MinDiskFreeMB, _ = l.config.Int64("log.min_disk_free_mb")
|
|
||||||
cfg.FlushIntervalMs, _ = l.config.Int64("log.flush_interval_ms")
|
|
||||||
cfg.TraceDepth, _ = l.config.Int64("log.trace_depth")
|
|
||||||
cfg.RetentionPeriodHrs, _ = l.config.Float64("log.retention_period_hrs")
|
|
||||||
cfg.RetentionCheckMins, _ = l.config.Float64("log.retention_check_mins")
|
|
||||||
cfg.DiskCheckIntervalMs, _ = l.config.Int64("log.disk_check_interval_ms")
|
|
||||||
cfg.EnableAdaptiveInterval, _ = l.config.Bool("log.enable_adaptive_interval")
|
|
||||||
cfg.MinCheckIntervalMs, _ = l.config.Int64("log.min_check_interval_ms")
|
|
||||||
cfg.MaxCheckIntervalMs, _ = l.config.Int64("log.max_check_interval_ms")
|
|
||||||
cfg.EnablePeriodicSync, _ = l.config.Bool("log.enable_periodic_sync")
|
|
||||||
cfg.HeartbeatLevel, _ = l.config.Int64("log.heartbeat_level")
|
|
||||||
cfg.HeartbeatIntervalS, _ = l.config.Int64("log.heartbeat_interval_s")
|
|
||||||
cfg.EnableStdout, _ = l.config.Bool("log.enable_stdout")
|
|
||||||
cfg.StdoutTarget, _ = l.config.String("log.stdout_target")
|
|
||||||
cfg.DisableFile, _ = l.config.Bool("log.disable_file")
|
|
||||||
return cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
// getCurrentLogChannel safely retrieves the current log channel
|
// getCurrentLogChannel safely retrieves the current log channel
|
||||||
func (l *Logger) getCurrentLogChannel() chan logRecord {
|
func (l *Logger) getCurrentLogChannel() chan logRecord {
|
||||||
chVal := l.state.ActiveLogChannel.Load()
|
chVal := l.state.ActiveLogChannel.Load()
|
||||||
@ -317,13 +207,12 @@ func (l *Logger) getCurrentLogChannel() chan logRecord {
|
|||||||
// getFlags from config
|
// getFlags from config
|
||||||
func (l *Logger) getFlags() int64 {
|
func (l *Logger) getFlags() int64 {
|
||||||
var flags int64 = 0
|
var flags int64 = 0
|
||||||
showLevel, _ := l.config.Bool("log.show_level")
|
cfg := l.getConfig()
|
||||||
showTimestamp, _ := l.config.Bool("log.show_timestamp")
|
|
||||||
|
|
||||||
if showLevel {
|
if cfg.ShowLevel {
|
||||||
flags |= FlagShowLevel
|
flags |= FlagShowLevel
|
||||||
}
|
}
|
||||||
if showTimestamp {
|
if cfg.ShowTimestamp {
|
||||||
flags |= FlagShowTimestamp
|
flags |= FlagShowTimestamp
|
||||||
}
|
}
|
||||||
return flags
|
return flags
|
||||||
@ -335,8 +224,8 @@ func (l *Logger) log(flags int64, level int64, depth int64, args ...any) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
configLevel, _ := l.config.Int64("log.level")
|
cfg := l.getConfig()
|
||||||
if level < configLevel {
|
if level < cfg.Level {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -411,11 +300,10 @@ func (l *Logger) handleFailedSend(record logRecord) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// internalLog handles writing internal logger diagnostics to stderr, if enabled.
|
// internalLog handles writing internal logger diagnostics to stderr, if enabled.
|
||||||
// This centralizes all internal error reporting and makes it configurable.
|
|
||||||
func (l *Logger) internalLog(format string, args ...any) {
|
func (l *Logger) internalLog(format string, args ...any) {
|
||||||
// Check if internal error reporting is enabled
|
// Check if internal error reporting is enabled
|
||||||
enabled, _ := l.config.Bool("log.internal_errors_to_stderr")
|
cfg := l.getConfig()
|
||||||
if !enabled {
|
if !cfg.InternalErrorsToStderr {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
70
processor.go
70
processor.go
@ -14,6 +14,8 @@ const (
|
|||||||
// Factors to adjust check interval
|
// Factors to adjust check interval
|
||||||
adaptiveIntervalFactor float64 = 1.5 // Slow down
|
adaptiveIntervalFactor float64 = 1.5 // Slow down
|
||||||
adaptiveSpeedUpFactor float64 = 0.8 // Speed up
|
adaptiveSpeedUpFactor float64 = 0.8 // Speed up
|
||||||
|
// Minimum wait time used throughout the package
|
||||||
|
minWaitTime = time.Duration(10 * time.Millisecond)
|
||||||
)
|
)
|
||||||
|
|
||||||
// processLogs is the main log processing loop running in a separate goroutine
|
// processLogs is the main log processing loop running in a separate goroutine
|
||||||
@ -25,14 +27,15 @@ func (l *Logger) processLogs(ch <-chan logRecord) {
|
|||||||
timers := l.setupProcessingTimers()
|
timers := l.setupProcessingTimers()
|
||||||
defer l.closeProcessingTimers(timers)
|
defer l.closeProcessingTimers(timers)
|
||||||
|
|
||||||
|
c := l.getConfig()
|
||||||
|
|
||||||
// Perform an initial disk check on startup (skip if file output is disabled)
|
// Perform an initial disk check on startup (skip if file output is disabled)
|
||||||
disableFile, _ := l.config.Bool("log.disable_file")
|
if !c.DisableFile {
|
||||||
if !disableFile {
|
|
||||||
l.performDiskCheck(true)
|
l.performDiskCheck(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send initial heartbeats immediately instead of waiting for first tick
|
// Send initial heartbeats immediately instead of waiting for first tick
|
||||||
heartbeatLevel, _ := l.config.Int64("log.heartbeat_level")
|
heartbeatLevel := c.HeartbeatLevel
|
||||||
if heartbeatLevel > 0 {
|
if heartbeatLevel > 0 {
|
||||||
if heartbeatLevel >= 1 {
|
if heartbeatLevel >= 1 {
|
||||||
l.logProcHeartbeat()
|
l.logProcHeartbeat()
|
||||||
@ -114,10 +117,12 @@ type TimerSet struct {
|
|||||||
func (l *Logger) setupProcessingTimers() *TimerSet {
|
func (l *Logger) setupProcessingTimers() *TimerSet {
|
||||||
timers := &TimerSet{}
|
timers := &TimerSet{}
|
||||||
|
|
||||||
|
c := l.getConfig()
|
||||||
|
|
||||||
// Set up flush timer
|
// Set up flush timer
|
||||||
flushInterval, _ := l.config.Int64("log.flush_interval_ms")
|
flushInterval := c.FlushIntervalMs
|
||||||
if flushInterval <= 0 {
|
if flushInterval <= 0 {
|
||||||
flushInterval = 100
|
flushInterval = DefaultConfig().FlushIntervalMs
|
||||||
}
|
}
|
||||||
timers.flushTicker = time.NewTicker(time.Duration(flushInterval) * time.Millisecond)
|
timers.flushTicker = time.NewTicker(time.Duration(flushInterval) * time.Millisecond)
|
||||||
|
|
||||||
@ -149,8 +154,9 @@ func (l *Logger) closeProcessingTimers(timers *TimerSet) {
|
|||||||
|
|
||||||
// setupRetentionTimer configures the retention check timer if retention is enabled
|
// setupRetentionTimer configures the retention check timer if retention is enabled
|
||||||
func (l *Logger) setupRetentionTimer(timers *TimerSet) <-chan time.Time {
|
func (l *Logger) setupRetentionTimer(timers *TimerSet) <-chan time.Time {
|
||||||
retentionPeriodHrs, _ := l.config.Float64("log.retention_period_hrs")
|
c := l.getConfig()
|
||||||
retentionCheckMins, _ := l.config.Float64("log.retention_check_mins")
|
retentionPeriodHrs := c.RetentionPeriodHrs
|
||||||
|
retentionCheckMins := c.RetentionCheckMins
|
||||||
retentionDur := time.Duration(retentionPeriodHrs * float64(time.Hour))
|
retentionDur := time.Duration(retentionPeriodHrs * float64(time.Hour))
|
||||||
retentionCheckInterval := time.Duration(retentionCheckMins * float64(time.Minute))
|
retentionCheckInterval := time.Duration(retentionCheckMins * float64(time.Minute))
|
||||||
|
|
||||||
@ -164,15 +170,16 @@ func (l *Logger) setupRetentionTimer(timers *TimerSet) <-chan time.Time {
|
|||||||
|
|
||||||
// setupDiskCheckTimer configures the disk check timer
|
// setupDiskCheckTimer configures the disk check timer
|
||||||
func (l *Logger) setupDiskCheckTimer() *time.Ticker {
|
func (l *Logger) setupDiskCheckTimer() *time.Ticker {
|
||||||
diskCheckIntervalMs, _ := l.config.Int64("log.disk_check_interval_ms")
|
c := l.getConfig()
|
||||||
|
diskCheckIntervalMs := c.DiskCheckIntervalMs
|
||||||
if diskCheckIntervalMs <= 0 {
|
if diskCheckIntervalMs <= 0 {
|
||||||
diskCheckIntervalMs = 5000
|
diskCheckIntervalMs = 5000
|
||||||
}
|
}
|
||||||
currentDiskCheckInterval := time.Duration(diskCheckIntervalMs) * time.Millisecond
|
currentDiskCheckInterval := time.Duration(diskCheckIntervalMs) * time.Millisecond
|
||||||
|
|
||||||
// Ensure initial interval respects bounds
|
// Ensure initial interval respects bounds
|
||||||
minCheckIntervalMs, _ := l.config.Int64("log.min_check_interval_ms")
|
minCheckIntervalMs := c.MinCheckIntervalMs
|
||||||
maxCheckIntervalMs, _ := l.config.Int64("log.max_check_interval_ms")
|
maxCheckIntervalMs := c.MaxCheckIntervalMs
|
||||||
minCheckInterval := time.Duration(minCheckIntervalMs) * time.Millisecond
|
minCheckInterval := time.Duration(minCheckIntervalMs) * time.Millisecond
|
||||||
maxCheckInterval := time.Duration(maxCheckIntervalMs) * time.Millisecond
|
maxCheckInterval := time.Duration(maxCheckIntervalMs) * time.Millisecond
|
||||||
|
|
||||||
@ -188,12 +195,13 @@ func (l *Logger) setupDiskCheckTimer() *time.Ticker {
|
|||||||
|
|
||||||
// setupHeartbeatTimer configures the heartbeat timer if heartbeats are enabled
|
// setupHeartbeatTimer configures the heartbeat timer if heartbeats are enabled
|
||||||
func (l *Logger) setupHeartbeatTimer(timers *TimerSet) <-chan time.Time {
|
func (l *Logger) setupHeartbeatTimer(timers *TimerSet) <-chan time.Time {
|
||||||
heartbeatLevel, _ := l.config.Int64("log.heartbeat_level")
|
c := l.getConfig()
|
||||||
|
heartbeatLevel := c.HeartbeatLevel
|
||||||
if heartbeatLevel > 0 {
|
if heartbeatLevel > 0 {
|
||||||
intervalS, _ := l.config.Int64("log.heartbeat_interval_s")
|
intervalS := c.HeartbeatIntervalS
|
||||||
// Make sure interval is positive
|
// Make sure interval is positive
|
||||||
if intervalS <= 0 {
|
if intervalS <= 0 {
|
||||||
intervalS = 60 // Default to 60 seconds
|
intervalS = DefaultConfig().HeartbeatIntervalS
|
||||||
}
|
}
|
||||||
timers.heartbeatTicker = time.NewTicker(time.Duration(intervalS) * time.Second)
|
timers.heartbeatTicker = time.NewTicker(time.Duration(intervalS) * time.Second)
|
||||||
return timers.heartbeatTicker.C
|
return timers.heartbeatTicker.C
|
||||||
@ -203,15 +211,16 @@ func (l *Logger) setupHeartbeatTimer(timers *TimerSet) <-chan time.Time {
|
|||||||
|
|
||||||
// processLogRecord handles individual log records, returning bytes written
|
// processLogRecord handles individual log records, returning bytes written
|
||||||
func (l *Logger) processLogRecord(record logRecord) int64 {
|
func (l *Logger) processLogRecord(record logRecord) int64 {
|
||||||
|
c := l.getConfig()
|
||||||
// Check if the record should process this record
|
// Check if the record should process this record
|
||||||
disableFile, _ := l.config.Bool("log.disable_file")
|
disableFile := c.DisableFile
|
||||||
if !disableFile && !l.state.DiskStatusOK.Load() {
|
if !disableFile && !l.state.DiskStatusOK.Load() {
|
||||||
l.state.DroppedLogs.Add(1)
|
l.state.DroppedLogs.Add(1)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize the log entry once
|
// Serialize the log entry once
|
||||||
format, _ := l.config.String("log.format")
|
format := c.Format
|
||||||
data := l.serializer.serialize(
|
data := l.serializer.serialize(
|
||||||
format,
|
format,
|
||||||
record.Flags,
|
record.Flags,
|
||||||
@ -223,7 +232,7 @@ func (l *Logger) processLogRecord(record logRecord) int64 {
|
|||||||
dataLen := int64(len(data))
|
dataLen := int64(len(data))
|
||||||
|
|
||||||
// Mirror to stdout if enabled
|
// Mirror to stdout if enabled
|
||||||
enableStdout, _ := l.config.Bool("log.enable_stdout")
|
enableStdout := c.EnableStdout
|
||||||
if enableStdout {
|
if enableStdout {
|
||||||
if s := l.state.StdoutWriter.Load(); s != nil {
|
if s := l.state.StdoutWriter.Load(); s != nil {
|
||||||
// Assert to concrete type: *sink
|
// Assert to concrete type: *sink
|
||||||
@ -244,7 +253,7 @@ func (l *Logger) processLogRecord(record logRecord) int64 {
|
|||||||
currentFileSize := l.state.CurrentSize.Load()
|
currentFileSize := l.state.CurrentSize.Load()
|
||||||
estimatedSize := currentFileSize + dataLen
|
estimatedSize := currentFileSize + dataLen
|
||||||
|
|
||||||
maxSizeMB, _ := l.config.Int64("log.max_size_mb")
|
maxSizeMB := c.MaxSizeMB
|
||||||
if maxSizeMB > 0 && estimatedSize > maxSizeMB*1024*1024 {
|
if maxSizeMB > 0 && estimatedSize > maxSizeMB*1024*1024 {
|
||||||
if err := l.rotateLogFile(); err != nil {
|
if err := l.rotateLogFile(); err != nil {
|
||||||
l.internalLog("failed to rotate log file: %v\n", err)
|
l.internalLog("failed to rotate log file: %v\n", err)
|
||||||
@ -276,7 +285,8 @@ func (l *Logger) processLogRecord(record logRecord) int64 {
|
|||||||
|
|
||||||
// handleFlushTick handles the periodic flush timer tick
|
// handleFlushTick handles the periodic flush timer tick
|
||||||
func (l *Logger) handleFlushTick() {
|
func (l *Logger) handleFlushTick() {
|
||||||
enableSync, _ := l.config.Bool("log.enable_periodic_sync")
|
c := l.getConfig()
|
||||||
|
enableSync := c.EnablePeriodicSync
|
||||||
if enableSync {
|
if enableSync {
|
||||||
l.performSync()
|
l.performSync()
|
||||||
}
|
}
|
||||||
@ -290,7 +300,8 @@ func (l *Logger) handleFlushRequest(confirmChan chan struct{}) {
|
|||||||
|
|
||||||
// handleRetentionCheck performs file retention check and cleanup
|
// handleRetentionCheck performs file retention check and cleanup
|
||||||
func (l *Logger) handleRetentionCheck() {
|
func (l *Logger) handleRetentionCheck() {
|
||||||
retentionPeriodHrs, _ := l.config.Float64("log.retention_period_hrs")
|
c := l.getConfig()
|
||||||
|
retentionPeriodHrs := c.RetentionPeriodHrs
|
||||||
retentionDur := time.Duration(retentionPeriodHrs * float64(time.Hour))
|
retentionDur := time.Duration(retentionPeriodHrs * float64(time.Hour))
|
||||||
|
|
||||||
if retentionDur > 0 {
|
if retentionDur > 0 {
|
||||||
@ -311,20 +322,21 @@ func (l *Logger) handleRetentionCheck() {
|
|||||||
|
|
||||||
// adjustDiskCheckInterval modifies the disk check interval based on logging activity
|
// adjustDiskCheckInterval modifies the disk check interval based on logging activity
|
||||||
func (l *Logger) adjustDiskCheckInterval(timers *TimerSet, lastCheckTime time.Time, logsSinceLastCheck int64) {
|
func (l *Logger) adjustDiskCheckInterval(timers *TimerSet, lastCheckTime time.Time, logsSinceLastCheck int64) {
|
||||||
enableAdaptive, _ := l.config.Bool("log.enable_adaptive_interval")
|
c := l.getConfig()
|
||||||
|
enableAdaptive := c.EnableAdaptiveInterval
|
||||||
if !enableAdaptive {
|
if !enableAdaptive {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
elapsed := time.Since(lastCheckTime)
|
elapsed := time.Since(lastCheckTime)
|
||||||
if elapsed < 10*time.Millisecond { // Min arbitrary reasonable value
|
if elapsed < minWaitTime { // Min arbitrary reasonable value
|
||||||
elapsed = 10 * time.Millisecond
|
elapsed = minWaitTime
|
||||||
}
|
}
|
||||||
|
|
||||||
logsPerSecond := float64(logsSinceLastCheck) / elapsed.Seconds()
|
logsPerSecond := float64(logsSinceLastCheck) / elapsed.Seconds()
|
||||||
targetLogsPerSecond := float64(100) // Baseline
|
targetLogsPerSecond := float64(100) // Baseline
|
||||||
|
|
||||||
diskCheckIntervalMs, _ := l.config.Int64("log.disk_check_interval_ms")
|
diskCheckIntervalMs := c.DiskCheckIntervalMs
|
||||||
currentDiskCheckInterval := time.Duration(diskCheckIntervalMs) * time.Millisecond
|
currentDiskCheckInterval := time.Duration(diskCheckIntervalMs) * time.Millisecond
|
||||||
|
|
||||||
// Calculate the new interval
|
// Calculate the new interval
|
||||||
@ -339,8 +351,8 @@ func (l *Logger) adjustDiskCheckInterval(timers *TimerSet, lastCheckTime time.Ti
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clamp interval using current config
|
// Clamp interval using current config
|
||||||
minCheckIntervalMs, _ := l.config.Int64("log.min_check_interval_ms")
|
minCheckIntervalMs := c.MinCheckIntervalMs
|
||||||
maxCheckIntervalMs, _ := l.config.Int64("log.max_check_interval_ms")
|
maxCheckIntervalMs := c.MaxCheckIntervalMs
|
||||||
minCheckInterval := time.Duration(minCheckIntervalMs) * time.Millisecond
|
minCheckInterval := time.Duration(minCheckIntervalMs) * time.Millisecond
|
||||||
maxCheckInterval := time.Duration(maxCheckIntervalMs) * time.Millisecond
|
maxCheckInterval := time.Duration(maxCheckIntervalMs) * time.Millisecond
|
||||||
|
|
||||||
@ -356,7 +368,8 @@ func (l *Logger) adjustDiskCheckInterval(timers *TimerSet, lastCheckTime time.Ti
|
|||||||
|
|
||||||
// handleHeartbeat processes a heartbeat timer tick
|
// handleHeartbeat processes a heartbeat timer tick
|
||||||
func (l *Logger) handleHeartbeat() {
|
func (l *Logger) handleHeartbeat() {
|
||||||
heartbeatLevel, _ := l.config.Int64("log.heartbeat_level")
|
c := l.getConfig()
|
||||||
|
heartbeatLevel := c.HeartbeatLevel
|
||||||
|
|
||||||
if heartbeatLevel >= 1 {
|
if heartbeatLevel >= 1 {
|
||||||
l.logProcHeartbeat()
|
l.logProcHeartbeat()
|
||||||
@ -401,8 +414,9 @@ func (l *Logger) logDiskHeartbeat() {
|
|||||||
rotations := l.state.TotalRotations.Load()
|
rotations := l.state.TotalRotations.Load()
|
||||||
deletions := l.state.TotalDeletions.Load()
|
deletions := l.state.TotalDeletions.Load()
|
||||||
|
|
||||||
dir, _ := l.config.String("log.directory")
|
c := l.getConfig()
|
||||||
ext, _ := l.config.String("log.extension")
|
dir := c.Directory
|
||||||
|
ext := c.Extension
|
||||||
currentSizeMB := float64(l.state.CurrentSize.Load()) / (1024 * 1024) // Current file size
|
currentSizeMB := float64(l.state.CurrentSize.Load()) / (1024 * 1024) // Current file size
|
||||||
totalSizeMB := float64(-1.0) // Default error value
|
totalSizeMB := float64(-1.0) // Default error value
|
||||||
fileCount := -1 // Default error value
|
fileCount := -1 // Default error value
|
||||||
|
|||||||
129
state.go
129
state.go
@ -2,15 +2,15 @@
|
|||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lixenwraith/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// State encapsulates the runtime state of the logger
|
// State encapsulates the runtime state of the logger
|
||||||
@ -46,11 +46,12 @@ type sink struct {
|
|||||||
w io.Writer
|
w io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initializes or reconfigures the logger using the provided config.Config instance
|
// Init initializes the logger using a map of configuration values
|
||||||
func (l *Logger) Init(cfg *config.Config, basePath string) error {
|
func (l *Logger) Init(values map[string]any) error {
|
||||||
if cfg == nil {
|
cfg, err := NewConfigFromDefaults(values)
|
||||||
|
if err != nil {
|
||||||
l.state.LoggerDisabled.Store(true)
|
l.state.LoggerDisabled.Store(true)
|
||||||
return fmtErrorf("config instance cannot be nil")
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
l.initMu.Lock()
|
l.initMu.Lock()
|
||||||
@ -60,71 +61,78 @@ func (l *Logger) Init(cfg *config.Config, basePath string) error {
|
|||||||
return fmtErrorf("logger previously failed to initialize and is disabled")
|
return fmtErrorf("logger previously failed to initialize and is disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := l.updateConfigFromExternal(cfg, basePath); err != nil {
|
return l.apply(cfg)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return l.applyConfig()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitWithDefaults initializes the logger with built-in defaults and optional overrides
|
// InitWithDefaults initializes the logger with built-in defaults and optional overrides
|
||||||
func (l *Logger) InitWithDefaults(overrides ...string) error {
|
func (l *Logger) InitWithDefaults(overrides ...string) error {
|
||||||
l.initMu.Lock()
|
// Parse overrides into a map
|
||||||
defer l.initMu.Unlock()
|
overrideMap := make(map[string]any)
|
||||||
|
|
||||||
if l.state.LoggerDisabled.Load() {
|
|
||||||
return fmtErrorf("logger previously failed to initialize and is disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
defaults := DefaultConfig()
|
||||||
for _, override := range overrides {
|
for _, override := range overrides {
|
||||||
key, valueStr, err := parseKeyValue(override)
|
key, valueStr, err := parseKeyValue(override)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
keyLower := strings.ToLower(key)
|
fieldType, err := getFieldType(defaults, key)
|
||||||
path := "log." + keyLower
|
|
||||||
|
|
||||||
if _, exists := l.config.Get(path); !exists {
|
|
||||||
return fmtErrorf("unknown config key in override: %s", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
currentVal, found := l.config.Get(path)
|
|
||||||
if !found {
|
|
||||||
return fmtErrorf("failed to get current value for '%s'", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
var parsedValue any
|
|
||||||
var parseErr error
|
|
||||||
|
|
||||||
switch currentVal.(type) {
|
|
||||||
case int64:
|
|
||||||
parsedValue, parseErr = strconv.ParseInt(valueStr, 10, 64)
|
|
||||||
case string:
|
|
||||||
parsedValue = valueStr
|
|
||||||
case bool:
|
|
||||||
parsedValue, parseErr = strconv.ParseBool(valueStr)
|
|
||||||
case float64:
|
|
||||||
parsedValue, parseErr = strconv.ParseFloat(valueStr, 64)
|
|
||||||
default:
|
|
||||||
return fmtErrorf("unsupported type for key '%s'", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
if parseErr != nil {
|
|
||||||
return fmtErrorf("invalid value format for '%s': %w", key, parseErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := validateConfigValue(keyLower, parsedValue); err != nil {
|
|
||||||
return fmtErrorf("invalid value for '%s': %w", key, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = l.config.Set(path, parsedValue)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmtErrorf("failed to update config value for '%s': %w", key, err)
|
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 l.applyConfig()
|
return "", fmt.Errorf("field not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown gracefully closes the logger, attempting to flush pending records
|
// Shutdown gracefully closes the logger, attempting to flush pending records
|
||||||
@ -154,17 +162,18 @@ func (l *Logger) Shutdown(timeout ...time.Duration) error {
|
|||||||
}
|
}
|
||||||
l.initMu.Unlock()
|
l.initMu.Unlock()
|
||||||
|
|
||||||
|
c := l.getConfig()
|
||||||
var effectiveTimeout time.Duration
|
var effectiveTimeout time.Duration
|
||||||
if len(timeout) > 0 {
|
if len(timeout) > 0 {
|
||||||
effectiveTimeout = timeout[0]
|
effectiveTimeout = timeout[0]
|
||||||
} else {
|
} else {
|
||||||
|
flushIntervalMs := c.FlushIntervalMs
|
||||||
// Default to 2x flush interval
|
// Default to 2x flush interval
|
||||||
flushMs, _ := l.config.Int64("log.flush_interval_ms")
|
effectiveTimeout = 2 * time.Duration(flushIntervalMs) * time.Millisecond
|
||||||
effectiveTimeout = 2 * time.Duration(flushMs) * time.Millisecond
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deadline := time.Now().Add(effectiveTimeout)
|
deadline := time.Now().Add(effectiveTimeout)
|
||||||
pollInterval := 10 * time.Millisecond // Reasonable check period
|
pollInterval := minWaitTime // Reasonable check period
|
||||||
processorCleanlyExited := false
|
processorCleanlyExited := false
|
||||||
for time.Now().Before(deadline) {
|
for time.Now().Before(deadline) {
|
||||||
if l.state.ProcessorExited.Load() {
|
if l.state.ProcessorExited.Load() {
|
||||||
@ -216,7 +225,7 @@ func (l *Logger) Flush(timeout time.Duration) error {
|
|||||||
select {
|
select {
|
||||||
case l.state.flushRequestChan <- confirmChan:
|
case l.state.flushRequestChan <- confirmChan:
|
||||||
// Request sent
|
// Request sent
|
||||||
case <-time.After(10 * time.Millisecond): // Short timeout to prevent blocking if processor is stuck
|
case <-time.After(minWaitTime): // Short timeout to prevent blocking if processor is stuck
|
||||||
return fmtErrorf("failed to send flush request to processor (possible deadlock or high load)")
|
return fmtErrorf("failed to send flush request to processor (possible deadlock or high load)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
89
storage.go
89
storage.go
@ -13,8 +13,9 @@ import (
|
|||||||
|
|
||||||
// performSync syncs the current log file
|
// performSync syncs the current log file
|
||||||
func (l *Logger) performSync() {
|
func (l *Logger) performSync() {
|
||||||
|
c := l.getConfig()
|
||||||
// Skip sync if file output is disabled
|
// Skip sync if file output is disabled
|
||||||
disableFile, _ := l.config.Bool("log.disable_file")
|
disableFile := c.DisableFile
|
||||||
if disableFile {
|
if disableFile {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -39,8 +40,9 @@ func (l *Logger) performSync() {
|
|||||||
// performDiskCheck checks disk space, triggers cleanup if needed, and updates status
|
// performDiskCheck checks disk space, triggers cleanup if needed, and updates status
|
||||||
// Returns true if disk is OK, false otherwise
|
// Returns true if disk is OK, false otherwise
|
||||||
func (l *Logger) performDiskCheck(forceCleanup bool) bool {
|
func (l *Logger) performDiskCheck(forceCleanup bool) bool {
|
||||||
|
c := l.getConfig()
|
||||||
// Skip all disk checks if file output is disabled
|
// Skip all disk checks if file output is disabled
|
||||||
disableFile, _ := l.config.Bool("log.disable_file")
|
disableFile := c.DisableFile
|
||||||
if disableFile {
|
if disableFile {
|
||||||
// Always return OK status when file output is disabled
|
// Always return OK status when file output is disabled
|
||||||
if !l.state.DiskStatusOK.Load() {
|
if !l.state.DiskStatusOK.Load() {
|
||||||
@ -50,10 +52,10 @@ func (l *Logger) performDiskCheck(forceCleanup bool) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
dir, _ := l.config.String("log.directory")
|
dir := c.Directory
|
||||||
ext, _ := l.config.String("log.extension")
|
ext := c.Extension
|
||||||
maxTotalMB, _ := l.config.Int64("log.max_total_size_mb")
|
maxTotalMB := c.MaxTotalSizeMB
|
||||||
minDiskFreeMB, _ := l.config.Int64("log.min_disk_free_mb")
|
minDiskFreeMB := c.MinDiskFreeMB
|
||||||
maxTotal := maxTotalMB * 1024 * 1024
|
maxTotal := maxTotalMB * 1024 * 1024
|
||||||
minFreeRequired := minDiskFreeMB * 1024 * 1024
|
minFreeRequired := minDiskFreeMB * 1024 * 1024
|
||||||
|
|
||||||
@ -156,7 +158,7 @@ func (l *Logger) getDiskFreeSpace(path string) (int64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getLogDirSize calculates total size of log files matching the current extension
|
// getLogDirSize calculates total size of log files matching the current extension
|
||||||
func (l *Logger) getLogDirSize(dir, fileExt string) (int64, error) {
|
func (l *Logger) getLogDirSize(dir, ext string) (int64, error) {
|
||||||
var size int64
|
var size int64
|
||||||
entries, err := os.ReadDir(dir)
|
entries, err := os.ReadDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -166,7 +168,7 @@ func (l *Logger) getLogDirSize(dir, fileExt string) (int64, error) {
|
|||||||
return 0, fmtErrorf("failed to read log directory '%s': %w", dir, err)
|
return 0, fmtErrorf("failed to read log directory '%s': %w", dir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
targetExt := "." + fileExt
|
targetExt := "." + ext
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
if entry.IsDir() {
|
if entry.IsDir() {
|
||||||
continue
|
continue
|
||||||
@ -184,9 +186,10 @@ func (l *Logger) getLogDirSize(dir, fileExt string) (int64, error) {
|
|||||||
|
|
||||||
// cleanOldLogs removes oldest log files until required space is freed
|
// cleanOldLogs removes oldest log files until required space is freed
|
||||||
func (l *Logger) cleanOldLogs(required int64) error {
|
func (l *Logger) cleanOldLogs(required int64) error {
|
||||||
dir, _ := l.config.String("log.directory")
|
c := l.getConfig()
|
||||||
fileExt, _ := l.config.String("log.extension")
|
dir := c.Directory
|
||||||
name, _ := l.config.String("log.name")
|
ext := c.Extension
|
||||||
|
name := c.Name
|
||||||
|
|
||||||
entries, err := os.ReadDir(dir)
|
entries, err := os.ReadDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -195,8 +198,8 @@ func (l *Logger) cleanOldLogs(required int64) error {
|
|||||||
|
|
||||||
// Get the static log filename to exclude from deletion
|
// Get the static log filename to exclude from deletion
|
||||||
staticLogName := name
|
staticLogName := name
|
||||||
if fileExt != "" {
|
if ext != "" {
|
||||||
staticLogName = name + "." + fileExt
|
staticLogName = name + "." + ext
|
||||||
}
|
}
|
||||||
|
|
||||||
type logFileMeta struct {
|
type logFileMeta struct {
|
||||||
@ -205,12 +208,12 @@ func (l *Logger) cleanOldLogs(required int64) error {
|
|||||||
size int64
|
size int64
|
||||||
}
|
}
|
||||||
var logs []logFileMeta
|
var logs []logFileMeta
|
||||||
targetExt := "." + fileExt
|
targetExt := "." + ext
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
if entry.IsDir() || entry.Name() == staticLogName {
|
if entry.IsDir() || entry.Name() == staticLogName {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if fileExt != "" && filepath.Ext(entry.Name()) != targetExt {
|
if ext != "" && filepath.Ext(entry.Name()) != targetExt {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
info, errInfo := entry.Info()
|
info, errInfo := entry.Info()
|
||||||
@ -251,9 +254,10 @@ func (l *Logger) cleanOldLogs(required int64) error {
|
|||||||
|
|
||||||
// updateEarliestFileTime scans the log directory for the oldest log file
|
// updateEarliestFileTime scans the log directory for the oldest log file
|
||||||
func (l *Logger) updateEarliestFileTime() {
|
func (l *Logger) updateEarliestFileTime() {
|
||||||
dir, _ := l.config.String("log.directory")
|
c := l.getConfig()
|
||||||
fileExt, _ := l.config.String("log.extension")
|
dir := c.Directory
|
||||||
name, _ := l.config.String("log.name")
|
ext := c.Extension
|
||||||
|
name := c.Name
|
||||||
|
|
||||||
entries, err := os.ReadDir(dir)
|
entries, err := os.ReadDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -264,11 +268,11 @@ func (l *Logger) updateEarliestFileTime() {
|
|||||||
var earliest time.Time
|
var earliest time.Time
|
||||||
// Get the active log filename to exclude from timestamp tracking
|
// Get the active log filename to exclude from timestamp tracking
|
||||||
staticLogName := name
|
staticLogName := name
|
||||||
if fileExt != "" {
|
if ext != "" {
|
||||||
staticLogName = name + "." + fileExt
|
staticLogName = name + "." + ext
|
||||||
}
|
}
|
||||||
|
|
||||||
targetExt := "." + fileExt
|
targetExt := "." + ext
|
||||||
prefix := name + "_"
|
prefix := name + "_"
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
if entry.IsDir() {
|
if entry.IsDir() {
|
||||||
@ -279,7 +283,7 @@ func (l *Logger) updateEarliestFileTime() {
|
|||||||
if fname == staticLogName {
|
if fname == staticLogName {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(fname, prefix) || (fileExt != "" && filepath.Ext(fname) != targetExt) {
|
if !strings.HasPrefix(fname, prefix) || (ext != "" && filepath.Ext(fname) != targetExt) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
info, errInfo := entry.Info()
|
info, errInfo := entry.Info()
|
||||||
@ -295,10 +299,11 @@ func (l *Logger) updateEarliestFileTime() {
|
|||||||
|
|
||||||
// cleanExpiredLogs removes log files older than the retention period
|
// cleanExpiredLogs removes log files older than the retention period
|
||||||
func (l *Logger) cleanExpiredLogs(oldest time.Time) error {
|
func (l *Logger) cleanExpiredLogs(oldest time.Time) error {
|
||||||
dir, _ := l.config.String("log.directory")
|
c := l.getConfig()
|
||||||
fileExt, _ := l.config.String("log.extension")
|
dir := c.Directory
|
||||||
name, _ := l.config.String("log.name")
|
ext := c.Extension
|
||||||
retentionPeriodHrs, _ := l.config.Float64("log.retention_period_hrs")
|
name := c.Name
|
||||||
|
retentionPeriodHrs := c.RetentionPeriodHrs
|
||||||
rpDuration := time.Duration(retentionPeriodHrs * float64(time.Hour))
|
rpDuration := time.Duration(retentionPeriodHrs * float64(time.Hour))
|
||||||
|
|
||||||
if rpDuration <= 0 {
|
if rpDuration <= 0 {
|
||||||
@ -316,18 +321,18 @@ func (l *Logger) cleanExpiredLogs(oldest time.Time) error {
|
|||||||
|
|
||||||
// Get the active log filename to exclude from deletion
|
// Get the active log filename to exclude from deletion
|
||||||
staticLogName := name
|
staticLogName := name
|
||||||
if fileExt != "" {
|
if ext != "" {
|
||||||
staticLogName = name + "." + fileExt
|
staticLogName = name + "." + ext
|
||||||
}
|
}
|
||||||
|
|
||||||
targetExt := "." + fileExt
|
targetExt := "." + ext
|
||||||
var deletedCount int
|
var deletedCount int
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
if entry.IsDir() || entry.Name() == staticLogName {
|
if entry.IsDir() || entry.Name() == staticLogName {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Only consider files with correct extension
|
// Only consider files with correct extension
|
||||||
if fileExt != "" && filepath.Ext(entry.Name()) != targetExt {
|
if ext != "" && filepath.Ext(entry.Name()) != targetExt {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
info, errInfo := entry.Info()
|
info, errInfo := entry.Info()
|
||||||
@ -345,17 +350,15 @@ func (l *Logger) cleanExpiredLogs(oldest time.Time) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if deletedCount == 0 && err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getStaticLogFilePath returns the full path to the active log file
|
// getStaticLogFilePath returns the full path to the active log file
|
||||||
func (l *Logger) getStaticLogFilePath() string {
|
func (l *Logger) getStaticLogFilePath() string {
|
||||||
dir, _ := l.config.String("log.directory")
|
c := l.getConfig()
|
||||||
name, _ := l.config.String("log.name")
|
dir := c.Directory
|
||||||
ext, _ := l.config.String("log.extension")
|
ext := c.Extension
|
||||||
|
name := c.Name
|
||||||
|
|
||||||
// Handle extension with or without dot
|
// Handle extension with or without dot
|
||||||
filename := name
|
filename := name
|
||||||
@ -368,8 +371,10 @@ func (l *Logger) getStaticLogFilePath() string {
|
|||||||
|
|
||||||
// generateArchiveLogFileName creates a timestamped filename for archived logs during rotation
|
// generateArchiveLogFileName creates a timestamped filename for archived logs during rotation
|
||||||
func (l *Logger) generateArchiveLogFileName(timestamp time.Time) string {
|
func (l *Logger) generateArchiveLogFileName(timestamp time.Time) string {
|
||||||
name, _ := l.config.String("log.name")
|
c := l.getConfig()
|
||||||
ext, _ := l.config.String("log.extension")
|
ext := c.Extension
|
||||||
|
name := c.Name
|
||||||
|
|
||||||
tsFormat := timestamp.Format("060102_150405")
|
tsFormat := timestamp.Format("060102_150405")
|
||||||
nano := timestamp.Nanosecond()
|
nano := timestamp.Nanosecond()
|
||||||
|
|
||||||
@ -393,6 +398,8 @@ func (l *Logger) createNewLogFile() (*os.File, error) {
|
|||||||
// rotateLogFile implements the rename-on-rotate strategy
|
// rotateLogFile implements the rename-on-rotate strategy
|
||||||
// Closes current file, renames it with timestamp, creates new static file
|
// Closes current file, renames it with timestamp, creates new static file
|
||||||
func (l *Logger) rotateLogFile() error {
|
func (l *Logger) rotateLogFile() error {
|
||||||
|
c := l.getConfig()
|
||||||
|
|
||||||
// Get current file handle
|
// Get current file handle
|
||||||
cfPtr := l.state.CurrentFile.Load()
|
cfPtr := l.state.CurrentFile.Load()
|
||||||
if cfPtr == nil {
|
if cfPtr == nil {
|
||||||
@ -427,7 +434,7 @@ func (l *Logger) rotateLogFile() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate archive filename with current timestamp
|
// Generate archive filename with current timestamp
|
||||||
dir, _ := l.config.String("log.directory")
|
dir := c.Directory
|
||||||
archiveName := l.generateArchiveLogFileName(time.Now())
|
archiveName := l.generateArchiveLogFileName(time.Now())
|
||||||
archivePath := filepath.Join(dir, archiveName)
|
archivePath := filepath.Join(dir, archiveName)
|
||||||
|
|
||||||
@ -459,7 +466,7 @@ func (l *Logger) rotateLogFile() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getLogFileCount calculates the number of log files matching the current extension
|
// getLogFileCount calculates the number of log files matching the current extension
|
||||||
func (l *Logger) getLogFileCount(dir, fileExt string) (int, error) {
|
func (l *Logger) getLogFileCount(dir, ext string) (int, error) {
|
||||||
count := 0
|
count := 0
|
||||||
entries, err := os.ReadDir(dir)
|
entries, err := os.ReadDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -469,7 +476,7 @@ func (l *Logger) getLogFileCount(dir, fileExt string) (int, error) {
|
|||||||
return -1, fmtErrorf("failed to read log directory '%s': %w", dir, err)
|
return -1, fmtErrorf("failed to read log directory '%s': %w", dir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
targetExt := "." + fileExt
|
targetExt := "." + ext
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
if entry.IsDir() {
|
if entry.IsDir() {
|
||||||
continue
|
continue
|
||||||
|
|||||||
Reference in New Issue
Block a user