e1.6.1 Configurable internal errors, minor documentation and code fixes.
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
# Log
|
||||
|
||||
[](https://golang.org)
|
||||
[](https://golang.org)
|
||||
[](https://opensource.org/licenses/BSD-3-Clause)
|
||||
[](doc/)
|
||||
|
||||
|
||||
@ -45,6 +45,9 @@ type Config struct {
|
||||
EnableStdout bool `toml:"enable_stdout"` // Mirror logs to stdout/stderr
|
||||
StdoutTarget string `toml:"stdout_target"` // "stdout" or "stderr"
|
||||
DisableFile bool `toml:"disable_file"` // Disable file output entirely
|
||||
|
||||
// Internal error handling
|
||||
InternalErrorsToStderr bool `toml:"internal_errors_to_stderr"` // Write internal errors to stderr
|
||||
}
|
||||
|
||||
// defaultConfig is the single source for all configurable default values
|
||||
@ -87,6 +90,9 @@ var defaultConfig = Config{
|
||||
EnableStdout: false,
|
||||
StdoutTarget: "stdout",
|
||||
DisableFile: false,
|
||||
|
||||
// Internal error handling
|
||||
InternalErrorsToStderr: false,
|
||||
}
|
||||
|
||||
// DefaultConfig returns a copy of the default configuration
|
||||
|
||||
@ -177,7 +177,7 @@ adapter := compat.NewFastHTTPAdapter(logger,
|
||||
if strings.Contains(msg, "performance") {
|
||||
return log.LevelWarn
|
||||
}
|
||||
// Return 0 to use default detection
|
||||
// Return 0 to use the adapter's default log level (log.LevelInfo by default)
|
||||
return 0
|
||||
}),
|
||||
)
|
||||
|
||||
@ -57,13 +57,14 @@ heartbeat_interval_s = 300
|
||||
|
||||
### Basic Settings
|
||||
|
||||
| Parameter | Type | Description | Default |
|
||||
|-----------|------|-------------|---------|
|
||||
| `level` | `int64` | Minimum log level (-4=Debug, 0=Info, 4=Warn, 8=Error) | `0` |
|
||||
| `name` | `string` | Base name for log files | `"log"` |
|
||||
| Parameter | Type | Description | Default |
|
||||
|-----------|------|-------------|------------|
|
||||
| `level` | `int64` | Minimum log level (-4=Debug, 0=Info, 4=Warn, 8=Error) | `0` |
|
||||
| `name` | `string` | Base name for log files | `"log"` |
|
||||
| `directory` | `string` | Directory to store log files | `"./logs"` |
|
||||
| `format` | `string` | Output format: `"txt"` or `"json"` | `"txt"` |
|
||||
| `extension` | `string` | Log file extension (without dot) | `"log"` |
|
||||
| `format` | `string` | Output format: `"txt"` or `"json"` | `"txt"` |
|
||||
| `extension` | `string` | Log file extension (without dot) | `"log"` |
|
||||
| `internal_errors_to_stderr` | `bool` | Write logger's internal errors to stderr | `false` |
|
||||
|
||||
### Output Control
|
||||
|
||||
@ -158,6 +159,7 @@ logger.InitWithDefaults(
|
||||
"format=json", // Structured for log aggregators
|
||||
"level=0", // Info level
|
||||
"show_timestamp=true", // Include timestamps
|
||||
"internal_errors_to_stderr=false", // Suppress internal errors
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@ -286,6 +286,50 @@ logger.Info("Batch processing",
|
||||
)
|
||||
```
|
||||
|
||||
## Internal Error Handling
|
||||
|
||||
The logger may encounter internal errors during operation (e.g., file rotation failures, disk space issues). By default, writing these errors to stderr is disabled, but can be enabled in configuration for diagnostic purposes.
|
||||
|
||||
### Controlling Internal Error Output
|
||||
|
||||
For applications requiring clean stderr output, keep internal error messages disabled:
|
||||
|
||||
```go
|
||||
logger.InitWithDefaults(
|
||||
"internal_errors_to_stderr=false", // Suppress internal diagnostics
|
||||
)
|
||||
```
|
||||
|
||||
### When to Keep Internal Errors Disabled
|
||||
|
||||
Consider disabling internal error output for:
|
||||
|
||||
- CLI tools producing structured output
|
||||
- Daemons with strict stderr requirements
|
||||
- Applications with custom error monitoring
|
||||
- Container environments with log aggregation
|
||||
|
||||
### Monitoring Without stderr
|
||||
|
||||
When internal errors are disabled, monitor logger health using:
|
||||
|
||||
1. **Heartbeat monitoring**: Detect issues via heartbeat logs
|
||||
```go
|
||||
logger.InitWithDefaults(
|
||||
"internal_errors_to_stderr=false",
|
||||
"heartbeat_level=2", // Include disk stats
|
||||
"heartbeat_interval_s=60",
|
||||
)
|
||||
```
|
||||
|
||||
2. **Check for dropped logs**: The logger tracks dropped messages
|
||||
```go
|
||||
// Dropped logs appear in regular log output when possible
|
||||
// Look for: "Logs were dropped" messages
|
||||
```
|
||||
|
||||
3. **External monitoring**: Monitor disk space and file system health independently
|
||||
|
||||
## Logging Patterns
|
||||
|
||||
### Request Lifecycle
|
||||
|
||||
@ -71,5 +71,5 @@ func customLevelDetector(msg string) int64 {
|
||||
}
|
||||
|
||||
// Use default detection
|
||||
return compat.detectLogLevel(msg)
|
||||
return compat.DetectLogLevel(msg)
|
||||
}
|
||||
@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
logDirectory = "./temp_logs"
|
||||
logDirectory = "./logs"
|
||||
logInterval = 200 * time.Millisecond // Shorter interval for quicker tests
|
||||
)
|
||||
|
||||
|
||||
28
logger.go
28
logger.go
@ -90,7 +90,7 @@ func (l *Logger) registerConfigValues() {
|
||||
// Register the entire config struct at once
|
||||
err := l.config.RegisterStruct("log.", defaultConfig)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "log: warning - failed to register config values: %v\n", err)
|
||||
l.internalLog("warning - failed to register config values: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,13 +150,13 @@ func (l *Logger) applyAndReconfigureLocked() error {
|
||||
minInterval, _ := l.config.Int64("log.min_check_interval_ms")
|
||||
maxInterval, _ := l.config.Int64("log.max_check_interval_ms")
|
||||
if minInterval > maxInterval {
|
||||
fmt.Fprintf(os.Stderr, "log: warning - min_check_interval_ms (%d) > max_check_interval_ms (%d), max will be used\n",
|
||||
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
|
||||
err := l.config.Set("log.min_check_interval_ms", maxInterval)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "log: warning - failed to update min_check_interval_ms: %v\n", err)
|
||||
l.internalLog("warning - failed to update min_check_interval_ms: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,7 +195,7 @@ func (l *Logger) applyAndReconfigureLocked() error {
|
||||
// Sync and close the file
|
||||
_ = currentFile.Sync()
|
||||
if err := currentFile.Close(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "log: warning - failed to close log file during disable: %v\n", err)
|
||||
l.internalLog("warning - failed to close log file during disable: %v\n", err)
|
||||
}
|
||||
}
|
||||
l.state.CurrentFile.Store((*os.File)(nil))
|
||||
@ -212,7 +212,7 @@ func (l *Logger) applyAndReconfigureLocked() error {
|
||||
if currentFile != nil && currentFile != logFile {
|
||||
_ = currentFile.Sync()
|
||||
if err := currentFile.Close(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "log: warning - failed to close old log file: %v\n", err)
|
||||
l.internalLog("warning - failed to close old log file: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -389,3 +389,21 @@ func (l *Logger) sendLogRecord(record logRecord) {
|
||||
l.state.DroppedLogs.Add(1)
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Check if internal error reporting is enabled
|
||||
enabled, _ := l.config.Bool("log.internal_errors_to_stderr")
|
||||
if !enabled {
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure consistent "log: " prefix
|
||||
if !strings.HasPrefix(format, "log: ") {
|
||||
format = "log: " + format
|
||||
}
|
||||
|
||||
// Write to stderr
|
||||
fmt.Fprintf(os.Stderr, format, args...)
|
||||
}
|
||||
21
processor.go
21
processor.go
@ -247,7 +247,10 @@ func (l *Logger) processLogRecord(record logRecord) int64 {
|
||||
maxSizeMB, _ := l.config.Int64("log.max_size_mb")
|
||||
if maxSizeMB > 0 && estimatedSize > maxSizeMB*1024*1024 {
|
||||
if err := l.rotateLogFile(); err != nil {
|
||||
fmtFprintf(os.Stderr, "log: failed to rotate log file: %v\n", err)
|
||||
l.internalLog("failed to rotate log file: %v\n", err)
|
||||
// Account for the dropped log that triggered the failed rotation
|
||||
l.state.DroppedLogs.Add(1)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
@ -256,7 +259,7 @@ func (l *Logger) processLogRecord(record logRecord) int64 {
|
||||
if currentLogFile, isFile := cfPtr.(*os.File); isFile && currentLogFile != nil {
|
||||
n, err := currentLogFile.Write(data)
|
||||
if err != nil {
|
||||
fmtFprintf(os.Stderr, "log: failed to write to log file: %v\n", err)
|
||||
l.internalLog("failed to write to log file: %v\n", err)
|
||||
l.state.DroppedLogs.Add(1)
|
||||
l.performDiskCheck(true)
|
||||
return 0
|
||||
@ -297,7 +300,7 @@ func (l *Logger) handleRetentionCheck() {
|
||||
if err := l.cleanExpiredLogs(earliest); err == nil {
|
||||
l.updateEarliestFileTime()
|
||||
} else {
|
||||
fmtFprintf(os.Stderr, "log: failed to clean expired logs: %v\n", err)
|
||||
l.internalLog("failed to clean expired logs: %v\n", err)
|
||||
}
|
||||
}
|
||||
} else if !ok || earliest.IsZero() {
|
||||
@ -408,14 +411,14 @@ func (l *Logger) logDiskHeartbeat() {
|
||||
if err == nil {
|
||||
totalSizeMB = float64(dirSize) / (1024 * 1024)
|
||||
} else {
|
||||
fmtFprintf(os.Stderr, "log: warning - heartbeat failed to get dir size: %v\n", err)
|
||||
l.internalLog("warning - heartbeat failed to get dir size: %v\n", err)
|
||||
}
|
||||
|
||||
count, err := l.getLogFileCount(dir, ext)
|
||||
if err == nil {
|
||||
fileCount = count
|
||||
} else {
|
||||
fmtFprintf(os.Stderr, "log: warning - heartbeat failed to get file count: %v\n", err)
|
||||
l.internalLog("warning - heartbeat failed to get file count: %v\n", err)
|
||||
}
|
||||
|
||||
diskArgs := []any{
|
||||
@ -489,25 +492,25 @@ func (l *Logger) writeHeartbeatRecord(level int64, args []any) {
|
||||
// Write to file
|
||||
cfPtr := l.state.CurrentFile.Load()
|
||||
if cfPtr == nil {
|
||||
fmtFprintf(os.Stderr, "log: error - current file handle is nil during heartbeat\n")
|
||||
l.internalLog("error - current file handle is nil during heartbeat\n")
|
||||
return
|
||||
}
|
||||
|
||||
currentLogFile, isFile := cfPtr.(*os.File)
|
||||
if !isFile || currentLogFile == nil {
|
||||
fmtFprintf(os.Stderr, "log: error - invalid file handle type during heartbeat\n")
|
||||
l.internalLog("error - invalid file handle type during heartbeat\n")
|
||||
return
|
||||
}
|
||||
|
||||
n, err := currentLogFile.Write(hbData)
|
||||
if err != nil {
|
||||
fmtFprintf(os.Stderr, "log: failed to write heartbeat: %v\n", err)
|
||||
l.internalLog("failed to write heartbeat: %v\n", err)
|
||||
l.performDiskCheck(true) // Force disk check on write failure
|
||||
|
||||
// One retry after disk check
|
||||
n, err = currentLogFile.Write(hbData)
|
||||
if err != nil {
|
||||
fmtFprintf(os.Stderr, "log: failed to write heartbeat on retry: %v\n", err)
|
||||
l.internalLog("failed to write heartbeat on retry: %v\n", err)
|
||||
} else {
|
||||
l.state.CurrentSize.Add(int64(n))
|
||||
}
|
||||
|
||||
10
storage.go
10
storage.go
@ -67,7 +67,7 @@ func (l *Logger) performDiskCheck(forceCleanup bool) bool {
|
||||
|
||||
freeSpace, err := l.getDiskFreeSpace(dir)
|
||||
if err != nil {
|
||||
fmtFprintf(os.Stderr, "log: warning - failed to check free disk space for '%s': %v\n", dir, err)
|
||||
l.internalLog("warning - failed to check free disk space for '%s': %v\n", dir, err)
|
||||
if l.state.DiskStatusOK.Load() {
|
||||
l.state.DiskStatusOK.Store(false)
|
||||
}
|
||||
@ -84,7 +84,7 @@ func (l *Logger) performDiskCheck(forceCleanup bool) bool {
|
||||
if maxTotal > 0 {
|
||||
dirSize, err := l.getLogDirSize(dir, ext)
|
||||
if err != nil {
|
||||
fmtFprintf(os.Stderr, "log: warning - failed to check log directory size for '%s': %v\n", dir, err)
|
||||
l.internalLog("warning - failed to check log directory size for '%s': %v\n", dir, err)
|
||||
if l.state.DiskStatusOK.Load() {
|
||||
l.state.DiskStatusOK.Store(false)
|
||||
}
|
||||
@ -234,7 +234,7 @@ func (l *Logger) cleanOldLogs(required int64) error {
|
||||
}
|
||||
filePath := filepath.Join(dir, log.name)
|
||||
if err := os.Remove(filePath); err != nil {
|
||||
fmtFprintf(os.Stderr, "log: failed to remove old log file '%s': %v\n", filePath, err)
|
||||
l.internalLog("failed to remove old log file '%s': %v\n", filePath, err)
|
||||
continue
|
||||
}
|
||||
freedSpace += log.size
|
||||
@ -330,7 +330,7 @@ func (l *Logger) cleanExpiredLogs(oldest time.Time) error {
|
||||
if info.ModTime().Before(cutoffTime) {
|
||||
filePath := filepath.Join(dir, entry.Name())
|
||||
if err := os.Remove(filePath); err != nil {
|
||||
fmtFprintf(os.Stderr, "log: failed to remove expired log file '%s': %v\n", filePath, err)
|
||||
l.internalLog("failed to remove expired log file '%s': %v\n", filePath, err)
|
||||
} else {
|
||||
deletedCount++
|
||||
l.state.TotalDeletions.Add(1)
|
||||
@ -388,7 +388,7 @@ func (l *Logger) rotateLogFile() error {
|
||||
if oldFilePtr != nil {
|
||||
if oldFile, ok := oldFilePtr.(*os.File); ok && oldFile != nil {
|
||||
if err := oldFile.Close(); err != nil {
|
||||
fmtFprintf(os.Stderr, "log: failed to close old log file '%s': %v\n", oldFile.Name(), err)
|
||||
l.internalLog("failed to close old log file '%s': %v\n", oldFile.Name(), err)
|
||||
// Continue with new file anyway
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
@ -68,14 +67,6 @@ func fmtErrorf(format string, args ...any) error {
|
||||
return fmt.Errorf(format, args...)
|
||||
}
|
||||
|
||||
// fmtFprintf wrapper (used for internal errors)
|
||||
func fmtFprintf(w *os.File, format string, args ...any) {
|
||||
if !strings.HasPrefix(format, "log: ") {
|
||||
format = "log: " + format
|
||||
}
|
||||
fmt.Fprintf(w, format, args...)
|
||||
}
|
||||
|
||||
// combineErrors helper
|
||||
func combineErrors(err1, err2 error) error {
|
||||
if err1 == nil {
|
||||
|
||||
Reference in New Issue
Block a user