e3.0.0 Tests added, optimization, bug fixes, builder changed.
This commit is contained in:
130
state.go
130
state.go
@ -1,16 +1,14 @@
|
||||
// FILE: state.go
|
||||
// FILE: lixenwraith/log/state.go
|
||||
package log
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// State encapsulates the runtime state of the logger
|
||||
type State struct {
|
||||
// General state
|
||||
IsInitialized atomic.Bool
|
||||
LoggerDisabled atomic.Bool
|
||||
ShutdownCalled atomic.Bool
|
||||
@ -18,16 +16,21 @@ type State struct {
|
||||
DiskStatusOK atomic.Bool
|
||||
ProcessorExited atomic.Bool // Tracks if the processor goroutine is running or has exited
|
||||
|
||||
// Flushing state
|
||||
flushRequestChan chan chan struct{} // Channel to request a flush
|
||||
flushMutex sync.Mutex // Protect concurrent Flush calls
|
||||
|
||||
CurrentFile atomic.Value // stores *os.File
|
||||
CurrentSize atomic.Int64 // Size of the current log file
|
||||
EarliestFileTime atomic.Value // stores time.Time for retention
|
||||
DroppedLogs atomic.Uint64 // Counter for logs dropped
|
||||
// Outputs
|
||||
CurrentFile atomic.Value // stores *os.File
|
||||
StdoutWriter atomic.Value // stores io.Writer (os.Stdout, os.Stderr, or io.Discard)
|
||||
|
||||
ActiveLogChannel atomic.Value // stores chan logRecord
|
||||
StdoutWriter atomic.Value // stores io.Writer (os.Stdout, os.Stderr, or io.Discard)
|
||||
// File State
|
||||
CurrentSize atomic.Int64 // Size of the current log file
|
||||
EarliestFileTime atomic.Value // stores time.Time for retention
|
||||
|
||||
// Log state
|
||||
ActiveLogChannel atomic.Value // stores chan logRecord
|
||||
DroppedLogs atomic.Uint64 // Counter for logs dropped
|
||||
|
||||
// Heartbeat statistics
|
||||
HeartbeatSequence atomic.Uint64 // Counter for heartbeat sequence numbers
|
||||
@ -35,111 +38,4 @@ type State struct {
|
||||
TotalLogsProcessed atomic.Uint64 // Counter for non-heartbeat logs successfully processed
|
||||
TotalRotations atomic.Uint64 // Counter for successful log rotations
|
||||
TotalDeletions atomic.Uint64 // Counter for successful log deletions (cleanup/retention)
|
||||
}
|
||||
|
||||
// sink is a wrapper around an io.Writer, atomic value type change workaround
|
||||
type sink struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
// Shutdown gracefully closes the logger, attempting to flush pending records
|
||||
// If no timeout is provided, uses a default of 2x flush interval
|
||||
func (l *Logger) Shutdown(timeout ...time.Duration) error {
|
||||
|
||||
if !l.state.ShutdownCalled.CompareAndSwap(false, true) {
|
||||
return nil
|
||||
}
|
||||
|
||||
l.state.LoggerDisabled.Store(true)
|
||||
|
||||
if !l.state.IsInitialized.Load() {
|
||||
l.state.ShutdownCalled.Store(false)
|
||||
l.state.LoggerDisabled.Store(false)
|
||||
l.state.ProcessorExited.Store(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
l.initMu.Lock()
|
||||
ch := l.getCurrentLogChannel()
|
||||
closedChan := make(chan logRecord)
|
||||
close(closedChan)
|
||||
l.state.ActiveLogChannel.Store(closedChan)
|
||||
if ch != closedChan {
|
||||
close(ch)
|
||||
}
|
||||
l.initMu.Unlock()
|
||||
|
||||
c := l.getConfig()
|
||||
var effectiveTimeout time.Duration
|
||||
if len(timeout) > 0 {
|
||||
effectiveTimeout = timeout[0]
|
||||
} else {
|
||||
flushIntervalMs := c.FlushIntervalMs
|
||||
// Default to 2x flush interval
|
||||
effectiveTimeout = 2 * time.Duration(flushIntervalMs) * time.Millisecond
|
||||
}
|
||||
|
||||
deadline := time.Now().Add(effectiveTimeout)
|
||||
pollInterval := minWaitTime // Reasonable check period
|
||||
processorCleanlyExited := false
|
||||
for time.Now().Before(deadline) {
|
||||
if l.state.ProcessorExited.Load() {
|
||||
processorCleanlyExited = true
|
||||
break
|
||||
}
|
||||
time.Sleep(pollInterval)
|
||||
}
|
||||
|
||||
l.state.IsInitialized.Store(false)
|
||||
|
||||
var finalErr error
|
||||
cfPtr := l.state.CurrentFile.Load()
|
||||
if cfPtr != nil {
|
||||
if currentLogFile, ok := cfPtr.(*os.File); ok && currentLogFile != nil {
|
||||
if err := currentLogFile.Sync(); err != nil {
|
||||
syncErr := fmtErrorf("failed to sync log file '%s' during shutdown: %w", currentLogFile.Name(), err)
|
||||
finalErr = combineErrors(finalErr, syncErr)
|
||||
}
|
||||
if err := currentLogFile.Close(); err != nil {
|
||||
closeErr := fmtErrorf("failed to close log file '%s' during shutdown: %w", currentLogFile.Name(), err)
|
||||
finalErr = combineErrors(finalErr, closeErr)
|
||||
}
|
||||
l.state.CurrentFile.Store((*os.File)(nil))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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(minWaitTime): // Short timeout to prevent blocking if processor is stuck
|
||||
return fmtErrorf("failed to send flush request to processor (possible deadlock or high load)")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-confirmChan:
|
||||
return nil
|
||||
case <-time.After(timeout):
|
||||
return fmtErrorf("timeout waiting for flush confirmation (%v)", timeout)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user