2022-12-06 21:45:50 +00:00
|
|
|
package file
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
|
|
|
// BufSize is the number of log that can be put in buffer before make it full
|
|
|
|
// BufSize= <value> _before_ calling NewFileHook
|
|
|
|
// Once the buffer is full, logging will start blocking, waiting for slots to
|
|
|
|
// be available in the queue.
|
|
|
|
var BufSize uint = 8192
|
|
|
|
|
2022-12-13 20:29:35 +00:00
|
|
|
// defaultFormater will be use if no formatter is given.
|
|
|
|
var defaultFormater = &logrus.TextFormatter{
|
|
|
|
DisableColors: true,
|
|
|
|
TimestampFormat: time.RFC3339Nano,
|
|
|
|
QuoteEmptyFields: true,
|
|
|
|
}
|
|
|
|
|
2022-12-06 21:45:50 +00:00
|
|
|
// Hook will write logs to a file.
|
|
|
|
type Hook struct {
|
|
|
|
Level logrus.Level
|
|
|
|
buf chan logrus.Entry
|
2022-12-10 14:09:00 +00:00
|
|
|
f logrus.Formatter
|
2022-12-06 21:45:50 +00:00
|
|
|
mu sync.RWMutex
|
|
|
|
synchronous bool
|
2022-12-10 14:09:00 +00:00
|
|
|
w io.Writer
|
|
|
|
wg sync.WaitGroup
|
2022-12-06 21:45:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewFileHook creates a hook to be added to an instance of logger.
|
2022-12-13 20:29:35 +00:00
|
|
|
func NewFileHook(w io.Writer, f logrus.Formatter) *Hook {
|
2022-12-06 21:45:50 +00:00
|
|
|
if w == nil {
|
|
|
|
logrus.Error("Can't create File Hook with an empty writer")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-12-13 20:29:35 +00:00
|
|
|
if f == nil {
|
|
|
|
f = defaultFormater
|
|
|
|
}
|
|
|
|
|
2022-12-06 21:45:50 +00:00
|
|
|
hook := &Hook{
|
|
|
|
Level: logrus.DebugLevel,
|
|
|
|
synchronous: true,
|
2022-12-13 20:29:35 +00:00
|
|
|
f: f,
|
2022-12-06 21:45:50 +00:00
|
|
|
w: w,
|
|
|
|
}
|
|
|
|
|
|
|
|
return hook
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewAsyncFileHook creates a hook to be added to an instance of logger.
|
|
|
|
// The hook created will be asynchronous, and it's the responsibility of the user to call the Flush method
|
|
|
|
// before exiting to empty the log queue.
|
2022-12-13 20:29:35 +00:00
|
|
|
func NewAsyncFileHook(w io.Writer, f logrus.Formatter) *Hook {
|
2022-12-06 21:45:50 +00:00
|
|
|
if w == nil {
|
|
|
|
logrus.Error("Can't create File Hook with an empty writer")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-12-13 20:29:35 +00:00
|
|
|
if f == nil {
|
|
|
|
f = defaultFormater
|
|
|
|
}
|
|
|
|
|
2022-12-06 21:45:50 +00:00
|
|
|
hook := &Hook{
|
|
|
|
Level: logrus.DebugLevel,
|
|
|
|
buf: make(chan logrus.Entry, BufSize),
|
2022-12-13 20:29:35 +00:00
|
|
|
f: f,
|
2022-12-06 21:45:50 +00:00
|
|
|
w: w,
|
|
|
|
}
|
2022-12-10 14:09:00 +00:00
|
|
|
|
2022-12-06 21:45:50 +00:00
|
|
|
go hook.fire() // Log in background
|
|
|
|
|
|
|
|
return hook
|
|
|
|
}
|
|
|
|
|
|
|
|
// Flush waits for the log queue to be empty.
|
|
|
|
// This func is meant to be used when the hook was created with NewAsyncFileHook.
|
|
|
|
func (hook *Hook) Flush() {
|
|
|
|
if hook.synchronous {
|
|
|
|
logrus.Error("Can't call Flush on a File Hook in synchronous execution")
|
|
|
|
}
|
|
|
|
|
|
|
|
hook.mu.Lock() // claim the mutex as a Lock - we want exclusive access to it
|
|
|
|
defer hook.mu.Unlock()
|
|
|
|
|
|
|
|
hook.wg.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fire is called when a log event is fired.
|
|
|
|
// We assume the entry will be altered by another hook,
|
|
|
|
// otherwise we might be logging something wrong to Graylog.
|
|
|
|
func (hook *Hook) Fire(entry *logrus.Entry) error {
|
|
|
|
hook.mu.RLock() // Claim the mutex as a RLock - allowing multiple go routines to log simultaneously
|
|
|
|
defer hook.mu.RUnlock()
|
|
|
|
|
|
|
|
if hook.synchronous {
|
|
|
|
hook.writeEntry(entry)
|
|
|
|
} else {
|
|
|
|
hook.wg.Add(1)
|
|
|
|
hook.buf <- *entry
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// fire will loop on the 'buf' channel, and write entries to the writer.
|
|
|
|
func (hook *Hook) fire() {
|
|
|
|
for {
|
|
|
|
entry := <-hook.buf // receive new entry on channel
|
|
|
|
hook.writeEntry(&entry)
|
|
|
|
hook.wg.Done()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// writeEntry write an entry to a file synchronously.
|
|
|
|
func (hook *Hook) writeEntry(entry *logrus.Entry) {
|
|
|
|
if hook.w == nil {
|
|
|
|
fmt.Println("Can't write to a file without a writer")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-12-10 14:09:00 +00:00
|
|
|
message, err := hook.f.Format(entry)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "Failed to format log line. Error: %s\n", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = hook.w.Write(message)
|
2022-12-06 21:45:50 +00:00
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "Failed to write in log file. Error: %s\nLog Line: %s", err, message)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Levels returns the available logging levels.
|
|
|
|
func (hook *Hook) Levels() []logrus.Level {
|
|
|
|
levels := []logrus.Level{}
|
|
|
|
for _, level := range logrus.AllLevels {
|
|
|
|
if level <= hook.Level {
|
|
|
|
levels = append(levels, level)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return levels
|
|
|
|
}
|