I am trying to write a web application which will run on a remote server. I need to log to capture errors/debug/audit. I find that multiple logging packages are available for golang including the standard "log" package. However, I need to fulfill three requirements:
The main logrotate configuration file is located at /etc/logrotate. conf . The file contains the default parameters that logrotate uses when it rotates logs. The file is commented, so you can skim it to see how the configuration is set up.
To verify if a particular log is indeed rotating or not and to check the last date and time of its rotation, check the /var/lib/logrotate/status file. This is a neatly formatted file that contains the log file name and the date on which it was last rotated.
Each time a new log file is started, the numbers in the file names of old logfiles are increased by one, so the files "rotate" through the numbers (thus the name "log rotation"). Old logfiles whose number exceeds a threshold can then be deleted or archived off-line to save space.
Though @Crast has given a very good answer, I want to also bring to the notice - lumberjack logger by Nate Finch which I ended up using.
Here is how to use it:
go install
command on the folder.import ( "log" "github.com/natefinch/lumberjack" )
Outside of main, declare your log variable.
var errLog *log.Logger
Inside main:
e, err := os.OpenFile("./foo.log", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) if err != nil { fmt.Printf("error opening file: %v", err) os.Exit(1) } errLog = log.New(e, "", log.Ldate|log.Ltime) errLog.SetOutput(&lumberjack.Logger{ Filename: "./foo.log", MaxSize: 1, // megabytes after which new file is created MaxBackups: 3, // number of backups MaxAge: 28, //days })
Now as soon as the file size get 1MB, a new file is created to keep the previous logs with the current timestamps, and the new logs will continue to log into foo.log file. Also, I have created the file using os.OpenFile but you may not need it as lumberjack internally does it, but I preferred it that way. Thanks, hope it helps. Once again thanks to @Crast and NateFinch.
The best way to fulfill all your three requirements instead of creating an alternate logger struct, if you were satisfied using the base-level log.Log, is instead to set the output of the logger to your own io.Writer instance.
So basically what I'm going to do here is show an example where I create my own io.Writer:
import ( "os" "sync" "time" ) type RotateWriter struct { lock sync.Mutex filename string // should be set to the actual filename fp *os.File } // Make a new RotateWriter. Return nil if error occurs during setup. func New(filename string) *RotateWriter { w := &RotateWriter{filename: filename} err := w.Rotate() if err != nil { return nil } return w } // Write satisfies the io.Writer interface. func (w *RotateWriter) Write(output []byte) (int, error) { w.lock.Lock() defer w.lock.Unlock() return w.fp.Write(output) } // Perform the actual act of rotating and reopening file. func (w *RotateWriter) Rotate() (err error) { w.lock.Lock() defer w.lock.Unlock() // Close existing file if open if w.fp != nil { err = w.fp.Close() w.fp = nil if err != nil { return } } // Rename dest file if it already exists _, err = os.Stat(w.filename) if err == nil { err = os.Rename(w.filename, w.filename+"."+time.Now().Format(time.RFC3339)) if err != nil { return } } // Create a file. w.fp, err = os.Create(w.filename) return }
You then create a RotateWriter and use log.SetOutput
to set this writer (if other packages are using the standard logger instance) or alternately create your own instances using log.New
to pass around.
I haven't solved the situation of when to call Rotate, I'll leave that to you to decide. It'd be fairly simple to trigger it based on time, or alternately do so after some amount of writes or some amount of bytes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With