package logging import ( "bufio" "context" "encoding/json" "io" "os" "os/signal" "sync" "syscall" "github.com/fleetdm/fleet/v4/pkg/secure" lumberjack "gopkg.in/natefinch/lumberjack.v2" "github.com/go-kit/kit/log" "github.com/pkg/errors" ) type filesystemLogWriter struct { writer io.WriteCloser } // NewFilesystemLogWriter creates a log file for osquery status/result logs. // The logFile can be rotated by sending a `SIGHUP` signal to Fleet if // enableRotation is true // // The enableCompression argument is only used when enableRotation is true. func NewFilesystemLogWriter(path string, appLogger log.Logger, enableRotation bool, enableCompression bool) (*filesystemLogWriter, error) { // Fail early if the process does not have the necessary // permissions to open the file at path. file, err := openFile(path) if err != nil { return nil, errors.Wrap(err, "perm check") } if !enableRotation { // no log rotation, use "raw" bufio implementation return &filesystemLogWriter{ writer: newRawLogWriter(file), }, nil } // Use lumberjack logger that supports rotation file.Close() osquerydLogger := &lumberjack.Logger{ Filename: path, MaxSize: 500, // megabytes MaxBackups: 3, MaxAge: 28, //days Compress: enableCompression, } appLogger = log.With(appLogger, "component", "osqueryd-logger") sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGHUP) go func() { for { <-sig //block on signal if err := osquerydLogger.Rotate(); err != nil { appLogger.Log("err", err) } } }() return &filesystemLogWriter{osquerydLogger}, nil } // If writer is based on bufio we want to flush after a batch of // writes so log entry gets completely written to the logfile. type flusher interface { Flush() error } // Write writes the provided logs to the filesystem func (l *filesystemLogWriter) Write(ctx context.Context, logs []json.RawMessage) error { for _, log := range logs { // Add newline to separate logs in output file log = append(log, '\n') if _, err := l.writer.Write(log); err != nil { return errors.Wrap(err, "writing log") } } if flusher, ok := l.writer.(flusher); ok { if err := flusher.Flush(); err != nil { return errors.Wrap(err, "flushing log") } } return nil } // rawLogWriter implements writing to logs directly through bufio type rawLogWriter struct { file *os.File buff *bufio.Writer mtx sync.Mutex } func newRawLogWriter(file *os.File) *rawLogWriter { buff := bufio.NewWriter(file) return &rawLogWriter{file: file, buff: buff} } // Write bytes to file func (l *rawLogWriter) Write(b []byte) (int, error) { l.mtx.Lock() defer l.mtx.Unlock() if l.buff == nil || l.file == nil { return 0, errors.New("filesystemLogWriter: can't write to closed file") } if _, statErr := os.Stat(l.file.Name()); errors.Is(statErr, os.ErrNotExist) { f, err := secure.OpenFile(l.file.Name(), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) if err != nil { return 0, errors.Wrapf(err, "create file for filesystemLogWriter %s", l.file.Name()) } l.file = f l.buff = bufio.NewWriter(f) } return l.buff.Write(b) } // Flush writes all buffered bytes to log file func (l *rawLogWriter) Flush() error { l.mtx.Lock() defer l.mtx.Unlock() if l.buff == nil { return errors.New("can't write to a closed file") } return l.buff.Flush() } // Close log file func (l *rawLogWriter) Close() error { l.mtx.Lock() defer l.mtx.Unlock() if l.buff != nil { if err := l.buff.Flush(); err != nil { return err } l.buff = nil } if l.file != nil { if err := l.file.Close(); err != nil { return err } l.file = nil } return nil } func openFile(path string) (*os.File, error) { return os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) }