status-go/geth/params/logger.go

146 lines
3.2 KiB
Go

package params
import (
"bufio"
"fmt"
"io"
"os"
"path/filepath"
"sync"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
)
// Logger is wrapper for custom logging
type Logger struct {
sync.Mutex
logFile *os.File
observer chan string
started chan struct{}
stopped chan struct{}
stopFlag bool
}
var onceStartLogger sync.Once
// SetupLogger configs logger using parameters in config
func SetupLogger(config *NodeConfig) (nodeLogger *Logger, err error) {
if !config.LogEnabled {
return nil, nil
}
nodeLogger = &Logger{
started: make(chan struct{}, 1),
stopped: make(chan struct{}, 1),
}
onceStartLogger.Do(func() {
err = nodeLogger.createAndStartLogger(config)
})
return
}
// SetV allows to dynamically change log level of messages being written
func (l *Logger) SetV(logLevel string) {
glog.SetV(parseLogLevel(logLevel))
}
// Stop marks logger as stopped, forcing to relinquish hold
// on os.Stderr and restore it back to the original
func (l *Logger) Stop() (stopped chan struct{}) {
l.Lock()
defer l.Unlock()
l.stopFlag = true
stopped = l.stopped
return
}
// Observe registers extra writer where logs should be written to.
// This method is used in unit tests, and should NOT be relied upon otherwise.
func (l *Logger) Observe(observer chan string) (started chan struct{}) {
l.observer = observer
started = l.started
return
}
// createAndStartLogger initializes and starts logger by replacing os.Stderr with custom writer.
// Custom writer intercepts all requests to os.Stderr, then forwards to multiple readers, which
// include log file and the original os.Stderr (so that logs output on screen as well)
func (l *Logger) createAndStartLogger(config *NodeConfig) error {
// customize glog
glog.CopyStandardLogTo("INFO")
glog.SetToStderr(true)
glog.SetV(parseLogLevel(config.LogLevel))
// create log file
logFile, err := os.OpenFile(filepath.Join(config.DataDir, config.LogFile), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)
if err != nil {
return err
}
// inject reader to pipe all writes to Stderr
r, w, err := os.Pipe()
if err != nil {
return err
}
// replace Stderr
origStderr := os.Stderr
os.Stderr = w
scanner := bufio.NewScanner(r)
// configure writer, send to the original os.Stderr and log file
logWriter := io.MultiWriter(origStderr, logFile)
go func() {
defer func() { // restore original Stderr
os.Stderr = origStderr
logFile.Close()
close(l.stopped)
}()
// notify observer that it can start polling (unit test, normally)
close(l.started)
for scanner.Scan() {
fmt.Fprintln(logWriter, scanner.Text())
if l.observer != nil {
l.observer <- scanner.Text()
}
// allow to restore original os.Stderr if logger is stopped
if l.stopFlag {
return
}
}
if err := scanner.Err(); err != nil {
fmt.Fprintf(origStderr, "error reading logs: %v\n", err)
}
}()
return nil
}
// parseLogLevel parses string and returns logger.* constant
func parseLogLevel(logLevel string) int {
switch logLevel {
case "ERROR":
return logger.Error
case "WARNING":
return logger.Warn
case "INFO":
return logger.Info
case "DEBUG":
return logger.Debug
case "DETAIL":
return logger.Detail
}
return logger.Info
}