diff --git a/client/internal/debug/debug.go b/client/internal/debug/debug.go index 7b8568464..6e82d9325 100644 --- a/client/internal/debug/debug.go +++ b/client/internal/debug/debug.go @@ -1043,7 +1043,8 @@ func (g *BundleGenerator) addRotatedLogFiles(logDir string) { return } - pattern := filepath.Join(logDir, "client-*.log.gz") + // This regex will match both logs rotated by us and logrotate on linux + pattern := filepath.Join(logDir, "client*.log*.gz") files, err := filepath.Glob(pattern) if err != nil { log.Warnf("failed to glob rotated logs: %v", err) diff --git a/util/log.go b/util/log.go index b1de2d999..22b9aa702 100644 --- a/util/log.go +++ b/util/log.go @@ -59,7 +59,18 @@ func InitLogger(logger *log.Logger, logLevel string, logs ...string) error { case "": logger.Warnf("empty log path received: %#v", logPath) default: - writers = append(writers, newRotatedOutput(logPath)) + conflict, configPath := FindFirstLogrotateConflict() + if conflict { + logger.Warnf("logrotation conflict detected in: %#v, rotation is disabled", configPath) + file, err := openOrCreateFile(logPath) + if err != nil { + logger.Errorf("Failed opening log file: %s", err) + return err + } + writers = append(writers, file) + } else { + writers = append(writers, newRotatedOutput(logPath)) + } } } diff --git a/util/logrotate_linux.go b/util/logrotate_linux.go new file mode 100644 index 000000000..17858db7b --- /dev/null +++ b/util/logrotate_linux.go @@ -0,0 +1,89 @@ +//go:build linux + +package util + +import ( + "bufio" + "errors" + "io/fs" + "os" + "path/filepath" + "strings" + + log "github.com/sirupsen/logrus" +) + +const ( + defaultLogrotateConfPath = "/etc/logrotate.conf" + defaultLogrotateConfDir = "/etc/logrotate.d" + netbirdString = "netbird" +) + +// FindLogrotateConflicts scans the standard logrotate locations for +// indications of conflict with netbird. It returns true and the config file +// path if a conflict was found. +func FindFirstLogrotateConflict() (bool, string) { + for _, f := range listLogrotateConfigs() { + present, err := scanLogrotateFile(f, netbirdString) + if err != nil { + if !errors.Is(err, fs.ErrNotExist) { + log.Debugf("scan %s: %v", f, err) + } + continue + } + if present { + return present, f + } + } + return false, "" +} + +// listLogrotateConfigs returns all config files for logrotate. +func listLogrotateConfigs() []string { + files := []string{defaultLogrotateConfPath} + entries, err := os.ReadDir(defaultLogrotateConfDir) + if err != nil { + return files + } + for _, e := range entries { + if e.IsDir() { + continue + } + files = append(files, filepath.Join(defaultLogrotateConfDir, e.Name())) + } + return files +} + +// scanLogrotateFile reads a config and reports if a non-comment line +// contains the given substring. +func scanLogrotateFile(path string, substring string) (bool, error) { + f, err := os.Open(path) + if err != nil { + return false, err + } + defer func() { + if err := f.Close(); err != nil { + log.Debugf("close %s: %v", path, err) + } + }() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := strings.TrimSpace(stripLogrotateComment(scanner.Text())) + if line == "" { + continue + } + if strings.Contains(line, substring) { + return true, nil + } + } + if err := scanner.Err(); err != nil { + return false, err + } + return false, nil +} + +func stripLogrotateComment(line string) string { + before, _, _ := strings.Cut(line, "#") + return before +} diff --git a/util/logrotate_nonlinux.go b/util/logrotate_nonlinux.go new file mode 100644 index 000000000..965609b08 --- /dev/null +++ b/util/logrotate_nonlinux.go @@ -0,0 +1,11 @@ +//go:build !linux + +package util + +// FindLogrotateConflicts scans the standard logrotate locations and returns +// true and the first config file that contains a non-comment line indicating +// it's configured for netbird. +// Will always return false for non-linux device. +func FindFirstLogrotateConflict() (bool, string) { + return false, "" +}