e1.0.1 Minor feature add, file restructure.
This commit is contained in:
43
README.md
43
README.md
@ -130,27 +130,28 @@ func main() {
|
|||||||
|
|
||||||
The `log` package is configured via keys registered with the `config.Config` instance passed to `log.Init`. `log.Init` expects these keys relative to the `basePath` argument.
|
The `log` package is configured via keys registered with the `config.Config` instance passed to `log.Init`. `log.Init` expects these keys relative to the `basePath` argument.
|
||||||
|
|
||||||
| Key (`basePath` + Key) | Type | Description | Default Value (Registered by `log.Init`) |
|
| Key (`basePath` + Key) | Type | Description | Default Value (Registered by `log.Init`) |
|
||||||
| :------------------------------ | :-------- | :------------------------------------------------------------------- | :--------------------------------------- |
|
|:---------------------------| :-------- |:-----------------------------------------------------------------|:-----------------------------------------|
|
||||||
| `level` | `int64` | Minimum log level (-4=Debug, 0=Info, 4=Warn, 8=Error) | `0` (LevelInfo) |
|
| `level` | `int64` | Minimum log level (-4=Debug, 0=Info, 4=Warn, 8=Error) | `0` (LevelInfo) |
|
||||||
| `name` | `string` | Base name for log files | `"log"` |
|
| `name` | `string` | Base name for log files | `"log"` |
|
||||||
| `directory` | `string` | Directory to store log files | `"./logs"` |
|
| `directory` | `string` | Directory to store log files | `"./logs"` |
|
||||||
| `format` | `string` | Log file format (`"txt"`, `"json"`) | `"txt"` |
|
| `format` | `string` | Log file format (`"txt"`, `"json"`) | `"txt"` |
|
||||||
| `extension` | `string` | Log file extension (e.g., `"log"`, `"app"`) | `"log"` |
|
| `extension` | `string` | Log file extension (e.g., `"log"`, `"app"`) | `"log"` |
|
||||||
| `show_timestamp` | `bool` | Show timestamp in log entries | `true` |
|
| `show_timestamp` | `bool` | Show timestamp in log entries | `true` |
|
||||||
| `show_level` | `bool` | Show log level in entries | `true` |
|
| `show_level` | `bool` | Show log level in entries | `true` |
|
||||||
| `buffer_size` | `int64` | Channel buffer capacity for log records | `1024` |
|
| `buffer_size` | `int64` | Channel buffer capacity for log records | `1024` |
|
||||||
| `max_size_mb` | `int64` | Max size (MB) per log file before rotation | `10` |
|
| `max_size_mb` | `int64` | Max size (MB) per log file before rotation | `10` |
|
||||||
| `max_total_size_mb` | `int64` | Max total size (MB) of log directory (0=unlimited) | `50` |
|
| `max_total_size_mb` | `int64` | Max total size (MB) of log directory (0=unlimited) | `50` |
|
||||||
| `min_disk_free_mb` | `int64` | Min required free disk space (MB) (0=unlimited) | `100` |
|
| `min_disk_free_mb` | `int64` | Min required free disk space (MB) (0=unlimited) | `100` |
|
||||||
| `flush_interval_ms` | `int64` | Interval (ms) to force flush buffer to disk via timer | `100` |
|
| `flush_interval_ms` | `int64` | Interval (ms) to force flush buffer to disk via timer | `100` |
|
||||||
| `trace_depth` | `int64` | Function call trace depth (0=disabled, 1-10) | `0` |
|
| `trace_depth` | `int64` | Function call trace depth (0=disabled, 1-10) | `0` |
|
||||||
| `retention_period_hrs` | `float64` | Hours to keep log files (0=disabled) | `0.0` |
|
| `retention_period_hrs` | `float64` | Hours to keep log files (0=disabled) | `0.0` |
|
||||||
| `retention_check_mins` | `float64` | Minutes between retention checks via timer (if enabled) | `60.0` |
|
| `retention_check_mins` | `float64` | Minutes between retention checks via timer (if enabled) | `60.0` |
|
||||||
| `disk_check_interval_ms` | `int64` | Base interval (ms) for periodic disk space checks via timer | `5000` |
|
| `disk_check_interval_ms` | `int64` | Base interval (ms) for periodic disk space checks via timer | `5000` |
|
||||||
| `enable_adaptive_interval` | `bool` | Adjust disk check interval based on load (within min/max bounds) | `true` |
|
| `enable_adaptive_interval` | `bool` | Adjust disk check interval based on load (within min/max bounds) | `true` |
|
||||||
| `min_check_interval_ms` | `int64` | Minimum interval (ms) for adaptive disk checks | `100` |
|
| `enable_periodic_sync` | `bool` | Periodic sync with disk based on flush interval | `false` |
|
||||||
| `max_check_interval_ms` | `int64` | Maximum interval (ms) for adaptive disk checks | `60000` |
|
| `min_check_interval_ms` | `int64` | Minimum interval (ms) for adaptive disk checks | `100` |
|
||||||
|
| `max_check_interval_ms` | `int64` | Maximum interval (ms) for adaptive disk checks | `60000` |
|
||||||
|
|
||||||
**Example TOML (`config.toml`)**
|
**Example TOML (`config.toml`)**
|
||||||
|
|
||||||
|
|||||||
@ -33,6 +33,7 @@ type Config struct {
|
|||||||
// Disk check settings
|
// Disk check settings
|
||||||
DiskCheckIntervalMs int64 `toml:"disk_check_interval_ms"` // Base interval for disk checks
|
DiskCheckIntervalMs int64 `toml:"disk_check_interval_ms"` // Base interval for disk checks
|
||||||
EnableAdaptiveInterval bool `toml:"enable_adaptive_interval"` // Adjust interval based on log rate
|
EnableAdaptiveInterval bool `toml:"enable_adaptive_interval"` // Adjust interval based on log rate
|
||||||
|
EnablePeriodicSync bool `toml:"enable_periodic_sync"` // Periodic sync with disk
|
||||||
MinCheckIntervalMs int64 `toml:"min_check_interval_ms"` // Minimum adaptive interval
|
MinCheckIntervalMs int64 `toml:"min_check_interval_ms"` // Minimum adaptive interval
|
||||||
MaxCheckIntervalMs int64 `toml:"max_check_interval_ms"` // Maximum adaptive interval
|
MaxCheckIntervalMs int64 `toml:"max_check_interval_ms"` // Maximum adaptive interval
|
||||||
}
|
}
|
||||||
@ -60,6 +61,7 @@ func DefaultConfig() *Config {
|
|||||||
RetentionCheckMins: 60.0,
|
RetentionCheckMins: 60.0,
|
||||||
DiskCheckIntervalMs: 5000,
|
DiskCheckIntervalMs: 5000,
|
||||||
EnableAdaptiveInterval: true,
|
EnableAdaptiveInterval: true,
|
||||||
|
EnablePeriodicSync: false,
|
||||||
MinCheckIntervalMs: 100,
|
MinCheckIntervalMs: 100,
|
||||||
MaxCheckIntervalMs: 60000,
|
MaxCheckIntervalMs: 60000,
|
||||||
}
|
}
|
||||||
|
|||||||
98
default.go
Normal file
98
default.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
// --- File: default.go ---
|
||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/LixenWraith/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Global instance for package-level functions
|
||||||
|
var defaultLogger = NewLogger()
|
||||||
|
|
||||||
|
// Default package-level functions that delegate to the default logger
|
||||||
|
|
||||||
|
// Init initializes or reconfigures the logger using the provided config.Config instance
|
||||||
|
func Init(cfg *config.Config, basePath string) error {
|
||||||
|
return defaultLogger.Init(cfg, basePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitWithDefaults initializes the logger with built-in defaults and optional overrides
|
||||||
|
func InitWithDefaults(overrides ...string) error {
|
||||||
|
return defaultLogger.InitWithDefaults(overrides...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown gracefully closes the logger, attempting to flush pending records
|
||||||
|
func Shutdown(timeout time.Duration) error {
|
||||||
|
return defaultLogger.Shutdown(timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug logs a message at debug level
|
||||||
|
func Debug(args ...any) {
|
||||||
|
defaultLogger.Debug(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info logs a message at info level
|
||||||
|
func Info(args ...any) {
|
||||||
|
defaultLogger.Info(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn logs a message at warning level
|
||||||
|
func Warn(args ...any) {
|
||||||
|
defaultLogger.Warn(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs a message at error level
|
||||||
|
func Error(args ...any) {
|
||||||
|
defaultLogger.Error(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DebugTrace logs a debug message with function call trace
|
||||||
|
func DebugTrace(depth int, args ...any) {
|
||||||
|
defaultLogger.DebugTrace(depth, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InfoTrace logs an info message with function call trace
|
||||||
|
func InfoTrace(depth int, args ...any) {
|
||||||
|
defaultLogger.InfoTrace(depth, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WarnTrace logs a warning message with function call trace
|
||||||
|
func WarnTrace(depth int, args ...any) {
|
||||||
|
defaultLogger.WarnTrace(depth, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorTrace logs an error message with function call trace
|
||||||
|
func ErrorTrace(depth int, args ...any) {
|
||||||
|
defaultLogger.ErrorTrace(depth, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log writes a timestamp-only record without level information
|
||||||
|
func Log(args ...any) {
|
||||||
|
defaultLogger.Log(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message writes a plain record without timestamp or level info
|
||||||
|
func Message(args ...any) {
|
||||||
|
defaultLogger.Message(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogTrace writes a timestamp record with call trace but no level info
|
||||||
|
func LogTrace(depth int, args ...any) {
|
||||||
|
defaultLogger.LogTrace(depth, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveConfig saves the current logger configuration to a file
|
||||||
|
func SaveConfig(path string) error {
|
||||||
|
return defaultLogger.SaveConfig(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadConfig loads logger configuration from a file with optional CLI overrides
|
||||||
|
func LoadConfig(path string, args []string) error {
|
||||||
|
return defaultLogger.LoadConfig(path, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush triggers a sync of the current log file buffer to disk and waits for completion or timeout
|
||||||
|
func Flush(timeout time.Duration) error {
|
||||||
|
return defaultLogger.Flush(timeout)
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
// format.go
|
// --- File: format.go ---
|
||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
1
go.mod
1
go.mod
@ -6,6 +6,5 @@ require github.com/LixenWraith/config v0.0.0-20250422065842-0c5b33a935d3
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/LixenWraith/tinytoml v0.0.0-20250422065624-8aa28720f04a // indirect
|
github.com/LixenWraith/tinytoml v0.0.0-20250422065624-8aa28720f04a // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
6
go.sum
6
go.sum
@ -1,12 +1,6 @@
|
|||||||
github.com/LixenWraith/config v0.0.0-20250421043933-12935fcc57a0 h1:HKd8Aj8EUHuLqVO9J+MeByPqUvJPAHZODSjVpyhnIrg=
|
|
||||||
github.com/LixenWraith/config v0.0.0-20250421043933-12935fcc57a0/go.mod h1:JF6kBabENV4uSgXd14tqt0DwvVS/9xxsxbU0xx+7yt8=
|
|
||||||
github.com/LixenWraith/config v0.0.0-20250422065842-0c5b33a935d3 h1:FosLYzJhQRB5skEvG50gZb5gALUS1zn7jzA6bWLxjB4=
|
github.com/LixenWraith/config v0.0.0-20250422065842-0c5b33a935d3 h1:FosLYzJhQRB5skEvG50gZb5gALUS1zn7jzA6bWLxjB4=
|
||||||
github.com/LixenWraith/config v0.0.0-20250422065842-0c5b33a935d3/go.mod h1:LWz2FXeYAN1IxmPFAmbMZLhL/5LbHzJgnj4m7l5jGvc=
|
github.com/LixenWraith/config v0.0.0-20250422065842-0c5b33a935d3/go.mod h1:LWz2FXeYAN1IxmPFAmbMZLhL/5LbHzJgnj4m7l5jGvc=
|
||||||
github.com/LixenWraith/tinytoml v0.0.0-20250305012228-6862ba843264 h1:p2hpE672qTRuhR9FAt7SIHp8aP0pJbBKushCiIRNRpo=
|
|
||||||
github.com/LixenWraith/tinytoml v0.0.0-20250305012228-6862ba843264/go.mod h1:pm+BQlZ/VQC30uaB5Vfeih2b77QkGIiMvu+QgG/XOTk=
|
|
||||||
github.com/LixenWraith/tinytoml v0.0.0-20250422065624-8aa28720f04a h1:m+lhpIexwlJa5m1QuEveRmaGIE+wp87T97PyX1IWbMw=
|
github.com/LixenWraith/tinytoml v0.0.0-20250422065624-8aa28720f04a h1:m+lhpIexwlJa5m1QuEveRmaGIE+wp87T97PyX1IWbMw=
|
||||||
github.com/LixenWraith/tinytoml v0.0.0-20250422065624-8aa28720f04a/go.mod h1:Vax79K0I//Klsa8POjua/XHbsMUiIdjJHr59VFbc0/8=
|
github.com/LixenWraith/tinytoml v0.0.0-20250422065624-8aa28720f04a/go.mod h1:Vax79K0I//Klsa8POjua/XHbsMUiIdjJHr59VFbc0/8=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
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/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
// --- File: interface.go ---
|
||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
343
logger.go
343
logger.go
@ -1,9 +1,9 @@
|
|||||||
|
// --- File: logger.go ---
|
||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -42,9 +42,6 @@ var configDefaults = map[string]interface{}{
|
|||||||
"log.max_check_interval_ms": int64(60000),
|
"log.max_check_interval_ms": int64(60000),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global instance for package-level functions
|
|
||||||
var defaultLogger = NewLogger()
|
|
||||||
|
|
||||||
// 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{
|
||||||
@ -70,9 +67,33 @@ func NewLogger() *Logger {
|
|||||||
close(initialChan)
|
close(initialChan)
|
||||||
l.state.ActiveLogChannel.Store(initialChan)
|
l.state.ActiveLogChannel.Store(initialChan)
|
||||||
|
|
||||||
|
l.state.flushRequestChan = make(chan chan struct{}, 1)
|
||||||
|
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadConfig loads logger configuration from a file with optional CLI overrides
|
||||||
|
func (l *Logger) LoadConfig(path string, args []string) error {
|
||||||
|
configExists, err := l.config.Load(path, args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no config file exists and no CLI args were provided, there's nothing to apply
|
||||||
|
if !configExists && len(args) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
l.initMu.Lock()
|
||||||
|
defer l.initMu.Unlock()
|
||||||
|
return l.applyAndReconfigureLocked()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveConfig saves the current logger configuration to a file
|
||||||
|
func (l *Logger) SaveConfig(path string) error {
|
||||||
|
return l.config.Save(path)
|
||||||
|
}
|
||||||
|
|
||||||
// registerConfigValues registers all configuration parameters with the config instance
|
// registerConfigValues registers all configuration parameters with the config instance
|
||||||
func (l *Logger) registerConfigValues() {
|
func (l *Logger) registerConfigValues() {
|
||||||
// Register each configuration value with its default
|
// Register each configuration value with its default
|
||||||
@ -85,35 +106,6 @@ func (l *Logger) registerConfigValues() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCurrentLogChannel safely retrieves the current log channel
|
|
||||||
func (l *Logger) getCurrentLogChannel() chan logRecord {
|
|
||||||
chVal := l.state.ActiveLogChannel.Load()
|
|
||||||
return chVal.(chan logRecord)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init initializes or reconfigures the logger using the provided config.Config instance
|
|
||||||
func (l *Logger) Init(cfg *config.Config, basePath string) error {
|
|
||||||
if cfg == nil {
|
|
||||||
l.state.LoggerDisabled.Store(true)
|
|
||||||
return fmtErrorf("config instance cannot be nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
l.initMu.Lock()
|
|
||||||
defer l.initMu.Unlock()
|
|
||||||
|
|
||||||
if l.state.LoggerDisabled.Load() {
|
|
||||||
return fmtErrorf("logger previously failed to initialize and is disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update configuration from external config
|
|
||||||
if err := l.updateConfigFromExternal(cfg, basePath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply configuration and reconfigure logger components
|
|
||||||
return l.applyAndReconfigureLocked()
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateConfigFromExternal updates the logger config from an external config.Config instance
|
// updateConfigFromExternal updates the logger config from an external config.Config instance
|
||||||
func (l *Logger) updateConfigFromExternal(extCfg *config.Config, basePath string) error {
|
func (l *Logger) updateConfigFromExternal(extCfg *config.Config, basePath string) error {
|
||||||
// For each config key, get value from external config and update local config
|
// For each config key, get value from external config and update local config
|
||||||
@ -160,73 +152,6 @@ func (l *Logger) updateConfigFromExternal(extCfg *config.Config, basePath string
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitWithDefaults initializes the logger with built-in defaults and optional overrides
|
|
||||||
func (l *Logger) InitWithDefaults(overrides ...string) error {
|
|
||||||
l.initMu.Lock()
|
|
||||||
defer l.initMu.Unlock()
|
|
||||||
|
|
||||||
if l.state.LoggerDisabled.Load() {
|
|
||||||
return fmtErrorf("logger previously failed to initialize and is disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply provided overrides
|
|
||||||
for _, override := range overrides {
|
|
||||||
key, valueStr, err := parseKeyValue(override)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
keyLower := strings.ToLower(key)
|
|
||||||
path := "log." + keyLower
|
|
||||||
|
|
||||||
// Check if this is a valid config key
|
|
||||||
if _, exists := l.config.Get(path); !exists {
|
|
||||||
return fmtErrorf("unknown config key in override: %s", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current value to determine type for parsing
|
|
||||||
currentVal, found := l.config.Get(path)
|
|
||||||
if !found {
|
|
||||||
return fmtErrorf("failed to get current value for '%s'", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse according to type
|
|
||||||
var parsedValue interface{}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the parsed value
|
|
||||||
if err := validateConfigValue(keyLower, parsedValue); err != nil {
|
|
||||||
return fmtErrorf("invalid value for '%s': %w", key, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update config with new value
|
|
||||||
err = l.config.Set(path, parsedValue)
|
|
||||||
if err != nil {
|
|
||||||
return fmtErrorf("failed to update config value for '%s': %w", key, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply configuration and reconfigure logger components
|
|
||||||
return l.applyAndReconfigureLocked()
|
|
||||||
}
|
|
||||||
|
|
||||||
// applyAndReconfigureLocked applies the configuration and reconfigures logger components
|
// applyAndReconfigureLocked applies the configuration and reconfigures logger components
|
||||||
// Assumes initMu is held
|
// Assumes initMu is held
|
||||||
func (l *Logger) applyAndReconfigureLocked() error {
|
func (l *Logger) applyAndReconfigureLocked() error {
|
||||||
@ -244,6 +169,13 @@ func (l *Logger) applyAndReconfigureLocked() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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")
|
dir, _ := l.config.String("log.directory")
|
||||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||||
@ -313,189 +245,36 @@ func (l *Logger) applyAndReconfigureLocked() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default package-level functions that delegate to the default logger
|
// loadCurrentConfig loads the current configuration for validation
|
||||||
|
func (l *Logger) loadCurrentConfig() *Config {
|
||||||
// Init initializes or reconfigures the logger using the provided config.Config instance
|
cfg := &Config{}
|
||||||
func Init(cfg *config.Config, basePath string) error {
|
cfg.Level, _ = l.config.Int64("log.level")
|
||||||
return defaultLogger.Init(cfg, basePath)
|
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.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")
|
||||||
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitWithDefaults initializes the logger with built-in defaults and optional overrides
|
// getCurrentLogChannel safely retrieves the current log channel
|
||||||
func InitWithDefaults(overrides ...string) error {
|
func (l *Logger) getCurrentLogChannel() chan logRecord {
|
||||||
return defaultLogger.InitWithDefaults(overrides...)
|
chVal := l.state.ActiveLogChannel.Load()
|
||||||
}
|
return chVal.(chan logRecord)
|
||||||
|
|
||||||
// Shutdown gracefully closes the logger, attempting to flush pending records
|
|
||||||
func Shutdown(timeout time.Duration) error {
|
|
||||||
return defaultLogger.Shutdown(timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug logs a message at debug level
|
|
||||||
func Debug(args ...any) {
|
|
||||||
defaultLogger.Debug(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info logs a message at info level
|
|
||||||
func Info(args ...any) {
|
|
||||||
defaultLogger.Info(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warn logs a message at warning level
|
|
||||||
func Warn(args ...any) {
|
|
||||||
defaultLogger.Warn(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error logs a message at error level
|
|
||||||
func Error(args ...any) {
|
|
||||||
defaultLogger.Error(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DebugTrace logs a debug message with function call trace
|
|
||||||
func DebugTrace(depth int, args ...any) {
|
|
||||||
defaultLogger.DebugTrace(depth, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InfoTrace logs an info message with function call trace
|
|
||||||
func InfoTrace(depth int, args ...any) {
|
|
||||||
defaultLogger.InfoTrace(depth, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WarnTrace logs a warning message with function call trace
|
|
||||||
func WarnTrace(depth int, args ...any) {
|
|
||||||
defaultLogger.WarnTrace(depth, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorTrace logs an error message with function call trace
|
|
||||||
func ErrorTrace(depth int, args ...any) {
|
|
||||||
defaultLogger.ErrorTrace(depth, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log writes a timestamp-only record without level information
|
|
||||||
func Log(args ...any) {
|
|
||||||
defaultLogger.Log(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message writes a plain record without timestamp or level info
|
|
||||||
func Message(args ...any) {
|
|
||||||
defaultLogger.Message(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LogTrace writes a timestamp record with call trace but no level info
|
|
||||||
func LogTrace(depth int, args ...any) {
|
|
||||||
defaultLogger.LogTrace(depth, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveConfig saves the current logger configuration to a file
|
|
||||||
func SaveConfig(path string) error {
|
|
||||||
return defaultLogger.SaveConfig(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadConfig loads logger configuration from a file with optional CLI overrides
|
|
||||||
func LoadConfig(path string, args []string) error {
|
|
||||||
return defaultLogger.LoadConfig(path, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveConfig saves the current logger configuration to a file
|
|
||||||
func (l *Logger) SaveConfig(path string) error {
|
|
||||||
return l.config.Save(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadConfig loads logger configuration from a file with optional CLI overrides
|
|
||||||
func (l *Logger) LoadConfig(path string, args []string) error {
|
|
||||||
configExists, err := l.config.Load(path, args)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no config file exists and no CLI args were provided, there's nothing to apply
|
|
||||||
if !configExists && len(args) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
l.initMu.Lock()
|
|
||||||
defer l.initMu.Unlock()
|
|
||||||
return l.applyAndReconfigureLocked()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper functions
|
|
||||||
func (l *Logger) Shutdown(timeout time.Duration) error {
|
|
||||||
// Ensure shutdown runs only once
|
|
||||||
if !l.state.ShutdownCalled.CompareAndSwap(false, true) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent new logs from being processed or sent
|
|
||||||
l.state.LoggerDisabled.Store(true)
|
|
||||||
|
|
||||||
// If the logger was never initialized, there's nothing to shut down
|
|
||||||
if !l.state.IsInitialized.Load() {
|
|
||||||
l.state.ShutdownCalled.Store(false) // Allow potential future init/shutdown cycle
|
|
||||||
l.state.LoggerDisabled.Store(false)
|
|
||||||
l.state.ProcessorExited.Store(true) // Mark as not running
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signal the processor goroutine to stop by closing its channel
|
|
||||||
l.initMu.Lock()
|
|
||||||
ch := l.getCurrentLogChannel()
|
|
||||||
closedChan := make(chan logRecord) // Create a dummy closed channel
|
|
||||||
close(closedChan)
|
|
||||||
l.state.ActiveLogChannel.Store(closedChan) // Point producers to the dummy channel
|
|
||||||
// Close the actual channel the processor is reading from
|
|
||||||
if ch != closedChan { // Avoid closing the dummy channel itself
|
|
||||||
close(ch)
|
|
||||||
}
|
|
||||||
l.initMu.Unlock()
|
|
||||||
|
|
||||||
// Determine the maximum time to wait for the processor to finish
|
|
||||||
effectiveTimeout := timeout
|
|
||||||
if effectiveTimeout <= 0 {
|
|
||||||
// Use the configured flush interval as the default timeout if none provided
|
|
||||||
flushMs, _ := l.config.Int64("log.flush_interval_ms")
|
|
||||||
effectiveTimeout = time.Duration(flushMs) * time.Millisecond
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the processor goroutine to signal its exit, or until the timeout
|
|
||||||
deadline := time.Now().Add(effectiveTimeout)
|
|
||||||
pollInterval := 10 * time.Millisecond // Check status periodically
|
|
||||||
processorCleanlyExited := false
|
|
||||||
for time.Now().Before(deadline) {
|
|
||||||
if l.state.ProcessorExited.Load() {
|
|
||||||
processorCleanlyExited = true
|
|
||||||
break // Processor finished cleanly
|
|
||||||
}
|
|
||||||
time.Sleep(pollInterval)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark the logger as uninitialized
|
|
||||||
l.state.IsInitialized.Store(false)
|
|
||||||
|
|
||||||
// Sync and close the current log file
|
|
||||||
var finalErr error
|
|
||||||
cfPtr := l.state.CurrentFile.Load()
|
|
||||||
if cfPtr != nil {
|
|
||||||
if currentLogFile, ok := cfPtr.(*os.File); ok && currentLogFile != nil {
|
|
||||||
// Attempt to sync data to disk
|
|
||||||
if err := currentLogFile.Sync(); err != nil {
|
|
||||||
finalErr = fmtErrorf("failed to sync log file '%s' during shutdown: %w", currentLogFile.Name(), err)
|
|
||||||
}
|
|
||||||
// Attempt to close the file descriptor
|
|
||||||
if err := currentLogFile.Close(); err != nil {
|
|
||||||
closeErr := fmtErrorf("failed to close log file '%s' during shutdown: %w", currentLogFile.Name(), err)
|
|
||||||
finalErr = combineErrors(finalErr, closeErr) // Combine sync/close errors
|
|
||||||
}
|
|
||||||
// Clear the atomic reference to the file
|
|
||||||
l.state.CurrentFile.Store((*os.File)(nil))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Report timeout error if processor didn't exit cleanly
|
|
||||||
if !processorCleanlyExited {
|
|
||||||
timeoutErr := fmtErrorf("logger processor did not exit within timeout (%v)", effectiveTimeout)
|
|
||||||
finalErr = combineErrors(finalErr, timeoutErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return finalErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logger instance methods for logging at different levels
|
// Logger instance methods for logging at different levels
|
||||||
@ -613,7 +392,7 @@ func (l *Logger) log(flags int64, level int64, depth int64, args ...any) {
|
|||||||
// Get trace if needed
|
// Get trace if needed
|
||||||
var trace string
|
var trace string
|
||||||
if depth > 0 {
|
if depth > 0 {
|
||||||
const skipTrace = 3 // log.Info -> logInternal -> getTrace (Adjust if call stack changes)
|
const skipTrace = 3 // log.Info -> log -> getTrace (Adjust if call stack changes)
|
||||||
trace = getTrace(depth, skipTrace)
|
trace = getTrace(depth, skipTrace)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
processor.go
11
processor.go
@ -1,4 +1,4 @@
|
|||||||
// processor.go
|
// --- File: processor.go ---
|
||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -146,7 +146,10 @@ func (l *Logger) processLogs(ch <-chan logRecord) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case <-flushTicker.C:
|
case <-flushTicker.C:
|
||||||
l.performSync()
|
enableSync, _ := l.config.Bool("log.enable_periodic_sync")
|
||||||
|
if enableSync {
|
||||||
|
l.performSync()
|
||||||
|
}
|
||||||
|
|
||||||
case <-diskCheckTicker.C:
|
case <-diskCheckTicker.C:
|
||||||
// Periodic disk check
|
// Periodic disk check
|
||||||
@ -188,6 +191,10 @@ func (l *Logger) processLogs(ch <-chan logRecord) {
|
|||||||
lastCheckTime = time.Now()
|
lastCheckTime = time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case confirmChan := <-l.state.flushRequestChan:
|
||||||
|
l.performSync()
|
||||||
|
close(confirmChan) // Signal completion back to the Flush caller
|
||||||
|
|
||||||
case <-retentionChan:
|
case <-retentionChan:
|
||||||
// Check file retention
|
// Check file retention
|
||||||
retentionPeriodHrs, _ := l.config.Float64("log.retention_period_hrs")
|
retentionPeriodHrs, _ := l.config.Float64("log.retention_period_hrs")
|
||||||
|
|||||||
213
state.go
213
state.go
@ -1,7 +1,15 @@
|
|||||||
|
// --- File: state.go ---
|
||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/LixenWraith/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// State encapsulates the runtime state of the logger
|
// State encapsulates the runtime state of the logger
|
||||||
@ -13,6 +21,9 @@ type State struct {
|
|||||||
DiskStatusOK atomic.Bool
|
DiskStatusOK atomic.Bool
|
||||||
ProcessorExited atomic.Bool // Tracks if the processor goroutine is running or has exited
|
ProcessorExited atomic.Bool // Tracks if the processor goroutine is running or has exited
|
||||||
|
|
||||||
|
flushRequestChan chan chan struct{} // Channel to request a flush
|
||||||
|
flushMutex sync.Mutex // Protect concurrent Flush calls
|
||||||
|
|
||||||
CurrentFile atomic.Value // stores *os.File
|
CurrentFile atomic.Value // stores *os.File
|
||||||
CurrentSize atomic.Int64 // Size of the current log file
|
CurrentSize atomic.Int64 // Size of the current log file
|
||||||
EarliestFileTime atomic.Value // stores time.Time for retention
|
EarliestFileTime atomic.Value // stores time.Time for retention
|
||||||
@ -21,3 +32,205 @@ type State struct {
|
|||||||
|
|
||||||
ActiveLogChannel atomic.Value // stores chan logRecord
|
ActiveLogChannel atomic.Value // stores chan logRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init initializes or reconfigures the logger using the provided config.Config instance
|
||||||
|
func (l *Logger) Init(cfg *config.Config, basePath string) error {
|
||||||
|
if cfg == nil {
|
||||||
|
l.state.LoggerDisabled.Store(true)
|
||||||
|
return fmtErrorf("config instance cannot be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
l.initMu.Lock()
|
||||||
|
defer l.initMu.Unlock()
|
||||||
|
|
||||||
|
if l.state.LoggerDisabled.Load() {
|
||||||
|
return fmtErrorf("logger previously failed to initialize and is disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update configuration from external config
|
||||||
|
if err := l.updateConfigFromExternal(cfg, basePath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply configuration and reconfigure logger components
|
||||||
|
return l.applyAndReconfigureLocked()
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitWithDefaults initializes the logger with built-in defaults and optional overrides
|
||||||
|
func (l *Logger) InitWithDefaults(overrides ...string) error {
|
||||||
|
l.initMu.Lock()
|
||||||
|
defer l.initMu.Unlock()
|
||||||
|
|
||||||
|
if l.state.LoggerDisabled.Load() {
|
||||||
|
return fmtErrorf("logger previously failed to initialize and is disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply provided overrides
|
||||||
|
for _, override := range overrides {
|
||||||
|
key, valueStr, err := parseKeyValue(override)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
keyLower := strings.ToLower(key)
|
||||||
|
path := "log." + keyLower
|
||||||
|
|
||||||
|
// Check if this is a valid config key
|
||||||
|
if _, exists := l.config.Get(path); !exists {
|
||||||
|
return fmtErrorf("unknown config key in override: %s", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current value to determine type for parsing
|
||||||
|
currentVal, found := l.config.Get(path)
|
||||||
|
if !found {
|
||||||
|
return fmtErrorf("failed to get current value for '%s'", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse according to type
|
||||||
|
var parsedValue interface{}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the parsed value
|
||||||
|
if err := validateConfigValue(keyLower, parsedValue); err != nil {
|
||||||
|
return fmtErrorf("invalid value for '%s': %w", key, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update config with new value
|
||||||
|
err = l.config.Set(path, parsedValue)
|
||||||
|
if err != nil {
|
||||||
|
return fmtErrorf("failed to update config value for '%s': %w", key, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply configuration and reconfigure logger components
|
||||||
|
return l.applyAndReconfigureLocked()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown gracefully closes the logger, attempting to flush pending records
|
||||||
|
func (l *Logger) Shutdown(timeout time.Duration) error {
|
||||||
|
// Ensure shutdown runs only once
|
||||||
|
if !l.state.ShutdownCalled.CompareAndSwap(false, true) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent new logs from being processed or sent
|
||||||
|
l.state.LoggerDisabled.Store(true)
|
||||||
|
|
||||||
|
// If the logger was never initialized, there's nothing to shut down
|
||||||
|
if !l.state.IsInitialized.Load() {
|
||||||
|
l.state.ShutdownCalled.Store(false) // Allow potential future init/shutdown cycle
|
||||||
|
l.state.LoggerDisabled.Store(false)
|
||||||
|
l.state.ProcessorExited.Store(true) // Mark as not running
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signal the processor goroutine to stop by closing its channel
|
||||||
|
l.initMu.Lock()
|
||||||
|
ch := l.getCurrentLogChannel()
|
||||||
|
closedChan := make(chan logRecord) // Create a dummy closed channel
|
||||||
|
close(closedChan)
|
||||||
|
l.state.ActiveLogChannel.Store(closedChan) // Point producers to the dummy channel
|
||||||
|
// Close the actual channel the processor is reading from
|
||||||
|
if ch != closedChan {
|
||||||
|
close(ch)
|
||||||
|
}
|
||||||
|
l.initMu.Unlock()
|
||||||
|
|
||||||
|
// Determine the maximum time to wait for the processor to finish
|
||||||
|
effectiveTimeout := timeout
|
||||||
|
if effectiveTimeout <= 0 {
|
||||||
|
// Use the configured flush interval as the default timeout if none provided
|
||||||
|
flushMs, _ := l.config.Int64("log.flush_interval_ms")
|
||||||
|
effectiveTimeout = 2 * time.Duration(flushMs) * time.Millisecond
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the processor goroutine to signal its exit, or until the timeout
|
||||||
|
deadline := time.Now().Add(effectiveTimeout)
|
||||||
|
pollInterval := 10 * time.Millisecond // Check status periodically
|
||||||
|
processorCleanlyExited := false
|
||||||
|
for time.Now().Before(deadline) {
|
||||||
|
if l.state.ProcessorExited.Load() {
|
||||||
|
processorCleanlyExited = true
|
||||||
|
break // Processor finished cleanly
|
||||||
|
}
|
||||||
|
time.Sleep(pollInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark the logger as uninitialized
|
||||||
|
l.state.IsInitialized.Store(false)
|
||||||
|
|
||||||
|
// Sync and close the current log file
|
||||||
|
var finalErr error
|
||||||
|
cfPtr := l.state.CurrentFile.Load()
|
||||||
|
if cfPtr != nil {
|
||||||
|
if currentLogFile, ok := cfPtr.(*os.File); ok && currentLogFile != nil {
|
||||||
|
// Attempt to sync data to disk
|
||||||
|
if err := currentLogFile.Sync(); err != nil {
|
||||||
|
syncErr := fmtErrorf("failed to sync log file '%s' during shutdown: %w", currentLogFile.Name(), err)
|
||||||
|
finalErr = combineErrors(finalErr, syncErr)
|
||||||
|
}
|
||||||
|
// Attempt to close the file descriptor
|
||||||
|
if err := currentLogFile.Close(); err != nil {
|
||||||
|
closeErr := fmtErrorf("failed to close log file '%s' during shutdown: %w", currentLogFile.Name(), err)
|
||||||
|
finalErr = combineErrors(finalErr, closeErr)
|
||||||
|
}
|
||||||
|
// Clear the atomic reference to the file
|
||||||
|
l.state.CurrentFile.Store((*os.File)(nil))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report timeout error if processor didn't exit cleanly
|
||||||
|
if !processorCleanlyExited {
|
||||||
|
timeoutErr := fmtErrorf("logger processor did not exit within timeout (%v)", effectiveTimeout)
|
||||||
|
finalErr = combineErrors(finalErr, timeoutErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush explicitly triggers a sync of the current log file buffer to disk and waits for completion or timeout.
|
||||||
|
func (l *Logger) Flush(timeout time.Duration) error {
|
||||||
|
// Prevent concurrent flushes overwhelming the processor or channel
|
||||||
|
l.state.flushMutex.Lock()
|
||||||
|
defer l.state.flushMutex.Unlock()
|
||||||
|
|
||||||
|
if !l.state.IsInitialized.Load() || l.state.ShutdownCalled.Load() {
|
||||||
|
return fmtErrorf("logger not initialized or already shut down")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a channel to wait for confirmation from the processor
|
||||||
|
confirmChan := make(chan struct{})
|
||||||
|
|
||||||
|
// Send the request with the confirmation channel
|
||||||
|
select {
|
||||||
|
case l.state.flushRequestChan <- confirmChan:
|
||||||
|
// Request sent
|
||||||
|
case <-time.After(10 * time.Millisecond): // Short timeout to prevent blocking if processor is stuck
|
||||||
|
return fmtErrorf("failed to send flush request to processor (possible deadlock or high load)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the processor to signal completion or timeout
|
||||||
|
select {
|
||||||
|
case <-confirmChan:
|
||||||
|
return nil // Flush completed successfully
|
||||||
|
case <-time.After(timeout):
|
||||||
|
return fmtErrorf("timeout waiting for flush confirmation (%v)", timeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
// --- File: utility.go ---
|
||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -141,8 +142,8 @@ func validateConfigValue(key string, value interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseLevel converts level string to numeric constant.
|
// Level converts level string to numeric constant.
|
||||||
func parseLevel(levelStr string) (int64, error) {
|
func Level(levelStr string) (int64, error) {
|
||||||
switch strings.ToLower(strings.TrimSpace(levelStr)) {
|
switch strings.ToLower(strings.TrimSpace(levelStr)) {
|
||||||
case "debug":
|
case "debug":
|
||||||
return LevelDebug, nil
|
return LevelDebug, nil
|
||||||
|
|||||||
Reference in New Issue
Block a user