summaryrefslogtreecommitdiff
path: root/internal/logger/logger.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/logger/logger.go')
-rw-r--r--internal/logger/logger.go457
1 files changed, 457 insertions, 0 deletions
diff --git a/internal/logger/logger.go b/internal/logger/logger.go
new file mode 100644
index 0000000..ca85e32
--- /dev/null
+++ b/internal/logger/logger.go
@@ -0,0 +1,457 @@
+package logger
+
+import (
+ "bufio"
+ "fmt"
+ "os"
+ "os/signal"
+ "runtime"
+ "strings"
+ "sync"
+ "syscall"
+ "time"
+
+ "github.com/mimecast/dtail/internal/color"
+ "github.com/mimecast/dtail/internal/config"
+)
+
+const (
+ clientStr string = "CLIENT"
+ serverStr string = "SERVER"
+ infoStr string = "INFO"
+ warnStr string = "WARN"
+ errorStr string = "ERROR"
+ fatalStr string = "FATAL"
+ debugStr string = "DEBUG"
+ traceStr string = "TRACE"
+)
+
+// Synchronise access to logging.
+var mutex sync.Mutex
+
+// File descriptor of log file when logToFile enabled.
+var fd *os.File
+
+// File write buffer of log file when logToFile enabled.
+var writer *bufio.Writer
+
+// File write buffer of stdout when logToStdout enabled.
+var stdoutWriter *bufio.Writer
+
+// Current hostname.
+var hostname string
+
+// Used to detect change of day (create one log file per day0
+var lastDateStr string
+
+// True if log in server mode, false if log in client mode.
+var serverEnable bool
+
+// Used to make logging non-blocking.
+var logBufCh chan buf
+var stdoutBufCh chan string
+
+// Stdout channel, required to pause output
+var pauseCh chan struct{}
+var resumeCh chan struct{}
+
+// Tell the logger that we are done, program shuts down
+var stop chan struct{}
+var stdoutFlushed chan struct{}
+
+// Tell the logger about logrotation
+var rotateCh chan os.Signal
+
+// LogMode allows to specify the verbosity of logging.
+type LogMode int
+
+// Possible log modes.
+const (
+ NormalMode LogMode = iota
+ DebugMode LogMode = iota
+ SilentMode LogMode = iota
+ TraceMode LogMode = iota
+ NothingMode LogMode = iota
+)
+
+// Mode is the current log mode in use.
+var Mode LogMode
+
+// LogStrategy allows to specify a log rotation strategy.
+type LogStrategy int
+
+// Possible log strategies.
+const (
+ NormalStrategy LogStrategy = iota
+ DailyStrategy LogStrategy = iota
+ StdoutStrategy LogStrategy = iota
+)
+
+// Strategy is the current log strattegy used.
+var Strategy LogStrategy
+
+// Enables logging to stdout.
+var logToStdout bool
+
+// Enables logging to file.
+var logToFile bool
+
+// Helper type to make logging non-blocking.
+type buf struct {
+ time time.Time
+ message string
+}
+
+// Start logging.
+func Start(myServerEnable, debugEnable, silentEnable, nothingEnable bool) {
+ serverEnable = myServerEnable
+
+ mode := logMode(debugEnable, silentEnable, nothingEnable)
+ strategy := logStrategy()
+
+ stdoutWriter = bufio.NewWriter(os.Stdout)
+ Mode = mode
+ Strategy = strategy
+
+ if Mode == NothingMode {
+ return
+ }
+
+ switch Strategy {
+ case DailyStrategy:
+ _, err := os.Stat(config.Common.LogDir)
+ logToFile = !os.IsNotExist(err)
+ logToStdout = !serverEnable || Mode == DebugMode || Mode == TraceMode
+ case StdoutStrategy:
+ fallthrough
+ default:
+ logToFile = false
+ logToStdout = true
+ }
+
+ fqdn, err := os.Hostname()
+ if err != nil {
+ panic(err)
+ }
+ s := strings.Split(fqdn, ".")
+ hostname = s[0]
+
+ pauseCh = make(chan struct{})
+ resumeCh = make(chan struct{})
+ stop = make(chan struct{})
+ stdoutFlushed = make(chan struct{})
+
+ // Setup logrotation
+ rotateCh = make(chan os.Signal, 1)
+ signal.Notify(rotateCh, syscall.SIGHUP)
+
+ if logToStdout {
+ stdoutBufCh = make(chan string, runtime.NumCPU()*100)
+ go writeToStdout()
+ }
+
+ if logToFile {
+ logBufCh = make(chan buf, runtime.NumCPU()*100)
+ go writeToFile()
+ }
+}
+
+func logMode(debugEnable, silentEnable, nothingEnable bool) LogMode {
+ switch {
+ case debugEnable:
+ return DebugMode
+ case nothingEnable:
+ return NothingMode
+ case config.Common.TraceEnable:
+ return TraceMode
+ case config.Common.DebugEnable:
+ return DebugMode
+ case silentEnable:
+ return SilentMode
+ default:
+ }
+ return NormalMode
+}
+
+func logStrategy() LogStrategy {
+ switch config.Common.LogStrategy {
+ case "daily":
+ return DailyStrategy
+ default:
+ }
+ return StdoutStrategy
+}
+
+// Info message logging.
+func Info(args ...interface{}) string {
+ if serverEnable {
+ return log(serverStr, infoStr, args)
+ }
+
+ return log(clientStr, infoStr, args)
+}
+
+// Warn message logging.
+func Warn(args ...interface{}) string {
+ if serverEnable {
+ return log(serverStr, warnStr, args)
+ }
+
+ return log(clientStr, warnStr, args)
+}
+
+// Error message logging.
+func Error(args ...interface{}) string {
+ if serverEnable {
+ return log(serverStr, errorStr, args)
+ }
+
+ return log(clientStr, errorStr, args)
+}
+
+// FatalExit logs an error and exists the process.
+func FatalExit(args ...interface{}) {
+ what := clientStr
+ if serverEnable {
+ what = serverStr
+ }
+ log(what, fatalStr, args)
+
+ time.Sleep(time.Second)
+ mutex.Lock()
+ defer mutex.Unlock()
+
+ closeWriter()
+ os.Exit(3)
+}
+
+// Debug message logging.
+func Debug(args ...interface{}) string {
+ if Mode == DebugMode || Mode == TraceMode {
+ if serverEnable {
+ return log(serverStr, debugStr, args)
+ }
+ return log(clientStr, debugStr, args)
+ }
+
+ return ""
+}
+
+// Trace message logging.
+func Trace(args ...interface{}) string {
+ if Mode == TraceMode {
+ if serverEnable {
+ return log(serverStr, traceStr, args)
+ }
+ return log(clientStr, traceStr, args)
+ }
+
+ return ""
+}
+
+// Write log line to buffer and/or log file.
+func write(what, severity, message string) {
+ if logToStdout && (Mode != SilentMode || severity != warnStr) {
+ line := fmt.Sprintf("%s|%s|%s|%s\n", what, hostname, severity, message)
+
+ if color.Colored {
+ line = color.Colorfy(line)
+ }
+
+ stdoutBufCh <- line
+ }
+
+ if logToFile {
+ t := time.Now()
+ timeStr := t.Format("20060102-150405")
+ logBufCh <- buf{
+ time: t,
+ message: fmt.Sprintf("%s|%s|%s|%s\n", severity, timeStr, what, message),
+ }
+ }
+}
+
+// Generig log message.
+func log(what string, severity string, args []interface{}) string {
+ if Mode == NothingMode {
+ return ""
+ }
+
+ var messages []string
+
+ for _, arg := range args {
+ switch v := arg.(type) {
+ case string:
+ messages = append(messages, v)
+ case int:
+ messages = append(messages, fmt.Sprintf("%d", v))
+ case error:
+ messages = append(messages, v.Error())
+ default:
+ messages = append(messages, fmt.Sprintf("%v", v))
+ }
+ }
+
+ message := strings.Join(messages, "|")
+ write(what, severity, message)
+
+ return fmt.Sprintf("%s|%s", severity, message)
+}
+
+// Raw message logging.
+func Raw(message string) {
+ if Mode == NothingMode {
+ return
+ }
+
+ if logToStdout {
+ if color.Colored {
+ message = color.Colorfy(message)
+ }
+ stdoutBufCh <- message
+ }
+
+ if logToFile {
+ logBufCh <- buf{time.Now(), message}
+ }
+}
+
+// Close log writer (e.g. on change of day).
+func closeWriter() {
+ if writer != nil {
+ writer.Flush()
+ fd.Close()
+ }
+}
+
+// Return the correct log file writer
+func fileWriter(dateStr string) *bufio.Writer {
+ if dateStr != lastDateStr {
+ return updateFileWriter(dateStr)
+ }
+
+ // Check for log rotation signal
+ select {
+ case <-rotateCh:
+ stdoutWriter.WriteString("Received signal for logrotation\n")
+ return updateFileWriter(dateStr)
+ default:
+ }
+
+ return writer
+}
+
+// Update log file writer
+func updateFileWriter(dateStr string) *bufio.Writer {
+ // Detected change of day. Close current writer and create a new one.
+ mutex.Lock()
+ defer mutex.Unlock()
+ closeWriter()
+
+ if _, err := os.Stat(config.Common.LogDir); os.IsNotExist(err) {
+ if err = os.MkdirAll(config.Common.LogDir, 0755); err != nil {
+ panic(err)
+ }
+ }
+
+ logFile := fmt.Sprintf("%s/%s.log", config.Common.LogDir, dateStr)
+ newFd, err := os.OpenFile(logFile, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
+ if err != nil {
+ panic(err)
+ }
+
+ fd = newFd
+ writer = bufio.NewWriterSize(fd, 1)
+ lastDateStr = dateStr
+
+ return writer
+}
+
+func flushStdout() {
+ defer close(stdoutFlushed)
+
+ for {
+ select {
+ case message := <-stdoutBufCh:
+ stdoutWriter.WriteString(message)
+ default:
+ stdoutWriter.Flush()
+ return
+ }
+ }
+}
+
+func writeToStdout() {
+ for {
+ select {
+ case message := <-stdoutBufCh:
+ stdoutWriter.WriteString(message)
+ case <-time.After(time.Millisecond * 100):
+ stdoutWriter.Flush()
+ case <-pauseCh:
+ PAUSE:
+ for {
+ select {
+ case <-stdoutBufCh:
+ case <-resumeCh:
+ break PAUSE
+ case <-stop:
+ return
+ }
+ }
+ case <-stop:
+ flushStdout()
+ return
+ }
+ }
+}
+
+func writeToFile() {
+ for {
+ select {
+ case buf := <-logBufCh:
+ dateStr := buf.time.Format("20060102")
+ w := fileWriter(dateStr)
+ w.WriteString(buf.message)
+ case <-pauseCh:
+ PAUSE:
+ for {
+ select {
+ case <-stdoutBufCh:
+ case <-resumeCh:
+ break PAUSE
+ case <-stop:
+ return
+ }
+ }
+ case <-stop:
+ return
+ }
+ }
+}
+
+// Pause logging.
+func Pause() {
+ if logToStdout {
+ pauseCh <- struct{}{}
+ }
+ if logToFile {
+ pauseCh <- struct{}{}
+ }
+}
+
+// Resume logging (after pausing).
+func Resume() {
+ if logToStdout {
+ resumeCh <- struct{}{}
+ }
+ if logToFile {
+ resumeCh <- struct{}{}
+ }
+}
+
+// Stop logging.
+func Stop() {
+ close(stop)
+ <-stdoutFlushed
+}