v0.1.11 harden logging lifecycle, sanitization, and formatter edge cases
Test / test (push) Canceled after 0s
Test / fuzz (./compat, FuzzParseFormat) (push) Canceled after 0s
Test / fuzz (./formatter, FuzzFormatJSON) (push) Canceled after 0s
Test / fuzz (./formatter, FuzzFormatTextPolicy) (push) Canceled after 0s
Test / fuzz (./sanitizer, FuzzSanitize) (push) Canceled after 0s
Test / fuzz (./sanitizer, FuzzSerializerJSON) (push) Canceled after 0s

This commit is contained in:
lixen committed 2026-09-26 14:25:14 -04:00
1 parent 79a81db3ca
commit 2417e51da1
46 files changed
+1846 -1289

No files matched your search

+46 -1
View File
@@ -2,6 +2,7 @@ package log
import (
"fmt"
"math"
"strconv"
"strings"
"time"
@@ -117,11 +118,24 @@ func (c *Config) Clone() *Config {
// Validate performs validation on the configuration
func (c *Config) Validate() error {
if c == nil {
return fmtErrorf("configuration cannot be nil")
}
// String validations
if strings.TrimSpace(c.Name) == "" {
return fmtErrorf("log name cannot be empty")
}
if c.Name == "." || c.Name == ".." || strings.ContainsAny(c.Name, "/\\\x00") || strings.ContainsAny(c.Extension, "/\\\x00") {
return fmtErrorf("name and extension must be filename components, without path separators or NUL")
}
if c.EnableFile && strings.TrimSpace(c.Directory) == "" {
return fmtErrorf("directory cannot be empty when file output is enabled")
}
if c.Level == levelOff {
return fmtErrorf("level is reserved for disabling emission")
}
if c.Format != "txt" && c.Format != "json" && c.Format != "raw" {
return fmtErrorf("invalid format: '%s' (use txt, json, or raw)", c.Format)
}
@@ -150,6 +164,14 @@ func (c *Config) Validate() error {
return fmtErrorf("buffer_size must be positive: %d", c.BufferSize)
}
// Bound allocation before make(chan): one million records already reserves
// over 100 MiB and is well beyond the default queue capacity.
if c.BufferSize > 1<<20 {
return fmtErrorf("buffer_size cannot exceed 1048576 records")
}
if c.MaxSizeKB > math.MaxInt64/sizeMultiplier || c.MaxTotalSizeKB > math.MaxInt64/sizeMultiplier || c.MinDiskFreeKB > math.MaxInt64/sizeMultiplier {
return fmtErrorf("size limits overflow byte counts")
}
if c.MaxSizeKB < 0 || c.MaxTotalSizeKB < 0 || c.MinDiskFreeKB < 0 {
return fmtErrorf("size limits cannot be negative")
}
@@ -159,6 +181,27 @@ func (c *Config) Validate() error {
return fmtErrorf("interval settings must be positive")
}
for _, ms := range []int64{c.FlushIntervalMs, c.DiskCheckIntervalMs, c.MinCheckIntervalMs, c.MaxCheckIntervalMs} {
if ms > math.MaxInt64/int64(time.Millisecond) {
return fmtErrorf("interval overflows time.Duration")
}
}
if c.FlushIntervalMs > math.MaxInt64/(2*int64(time.Millisecond)) {
return fmtErrorf("flush interval overflows default stop timeout")
}
if c.HeartbeatIntervalS > math.MaxInt64/int64(time.Second) {
return fmtErrorf("heartbeat interval overflows time.Duration")
}
for _, setting := range []struct{ value, unit float64 }{
{c.RetentionPeriodHrs, float64(time.Hour)},
{c.RetentionCheckMins, float64(time.Minute)},
} {
ns := setting.value * setting.unit
if math.IsNaN(ns) || math.IsInf(ns, 0) || ns >= float64(math.MaxInt64) || setting.value > 0 && ns < 1 {
return fmtErrorf("retention settings must be finite and representable as time.Duration")
}
}
if c.TraceDepth < 0 || c.TraceDepth > 10 {
return fmtErrorf("trace_depth must be between 0 and 10: %d", c.TraceDepth)
}
@@ -381,6 +424,8 @@ func configRequiresRestart(oldCfg, newCfg *Config) bool {
if oldCfg.FlushIntervalMs != newCfg.FlushIntervalMs ||
oldCfg.DiskCheckIntervalMs != newCfg.DiskCheckIntervalMs ||
oldCfg.EnableAdaptiveInterval != newCfg.EnableAdaptiveInterval ||
oldCfg.MinCheckIntervalMs != newCfg.MinCheckIntervalMs ||
oldCfg.MaxCheckIntervalMs != newCfg.MaxCheckIntervalMs ||
oldCfg.HeartbeatIntervalS != newCfg.HeartbeatIntervalS ||
oldCfg.HeartbeatLevel != newCfg.HeartbeatLevel ||
oldCfg.RetentionCheckMins != newCfg.RetentionCheckMins ||
@@ -411,4 +456,4 @@ func combineConfigErrors(errors []error) error {
sb.WriteString(fmt.Sprintf("\n %d. %s", i+1, errMsg))
}
return fmt.Errorf("%s", sb.String())
}
}