* glog: add gzip compression for rotated log files Add opt-in gzip compression that automatically compresses log files after rotation, reducing disk usage in long-running deployments. - Add --log_compress flag to enable compression at startup - Add SetCompressRotated()/IsCompressRotated() for runtime toggle - Compress rotated files in background goroutine (non-blocking) - Use gzip.BestSpeed for minimal CPU overhead - Fix .gz file cleanup: TrimSuffix approach correctly counts compressed files toward MaxFileCount limit - Include 6 unit tests covering normal, empty, large, and edge cases Enabled via --log_compress flag. Default behavior unchanged. * glog: fix compressFile to check gz/dst close errors and use atomic rename Write to a temp file (.gz.tmp) and rename atomically to prevent exposing partial archives. Check gz.Close() and dst.Close() errors to avoid deleting the original log when flush fails (e.g. ENOSPC). Use defer for robust resource cleanup. * glog: deduplicate .log/.log.gz pairs in rotation cleanup During concurrent compression, both foo.log and foo.log.gz can exist simultaneously. Count them as one entry against MaxFileCount to prevent premature eviction of rotated logs. * glog: use portable temp path in TestCompressFile_NonExistent Replace hardcoded /nonexistent/path with t.TempDir() for portability. --------- Co-authored-by: Copilot <copilot@github.com>
88 lines
1.7 KiB
Go
88 lines
1.7 KiB
Go
package glog
|
|
|
|
import (
|
|
"compress/gzip"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
"sync/atomic"
|
|
)
|
|
|
|
// CompressRotated controls whether rotated log files are gzip-compressed.
|
|
// 0 = disabled (default), 1 = enabled.
|
|
var compressRotated int32
|
|
|
|
// SetCompressRotated enables or disables gzip compression of rotated log files.
|
|
// When enabled, after a log file is rotated the old file is compressed in a
|
|
// background goroutine (non-blocking to the logging path).
|
|
func SetCompressRotated(enabled bool) {
|
|
if enabled {
|
|
atomic.StoreInt32(&compressRotated, 1)
|
|
} else {
|
|
atomic.StoreInt32(&compressRotated, 0)
|
|
}
|
|
}
|
|
|
|
// IsCompressRotated returns whether compression of rotated files is active.
|
|
func IsCompressRotated() bool {
|
|
return atomic.LoadInt32(&compressRotated) == 1
|
|
}
|
|
|
|
// compressFile compresses the given file with gzip and removes the original.
|
|
// This runs in a background goroutine to avoid blocking the log write path.
|
|
// If any step fails the original file is kept intact.
|
|
func compressFile(path string) {
|
|
// Skip if already compressed
|
|
if strings.HasSuffix(path, ".gz") {
|
|
return
|
|
}
|
|
|
|
src, err := os.Open(path)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer src.Close()
|
|
|
|
dstPath := path + ".gz"
|
|
tmpPath := dstPath + ".tmp"
|
|
dst, err := os.Create(tmpPath)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
var success bool
|
|
defer func() {
|
|
dst.Close()
|
|
if !success {
|
|
os.Remove(tmpPath)
|
|
}
|
|
}()
|
|
|
|
gz, err := gzip.NewWriterLevel(dst, gzip.BestSpeed)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if _, err = io.Copy(gz, src); err != nil {
|
|
gz.Close()
|
|
return
|
|
}
|
|
|
|
if err = gz.Close(); err != nil {
|
|
return
|
|
}
|
|
|
|
if err = dst.Close(); err != nil {
|
|
return
|
|
}
|
|
|
|
if err = os.Rename(tmpPath, dstPath); err != nil {
|
|
return
|
|
}
|
|
|
|
success = true
|
|
|
|
// Only remove original after successful compression
|
|
os.Remove(path)
|
|
}
|