123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- package log
- import (
- "fmt"
- loglib "github.com/sirupsen/logrus"
- "io"
- "io/ioutil"
- "net"
- "os"
- "sync"
- )
- // The following are taken from logrus
- const (
- // PanicLevel level, highest level of severity. Logs and then calls panic with the
- // message passed to Debug, Info, ...
- PanicLevel Level = iota
- // FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the
- // logging level is set to Panic.
- FatalLevel
- // ErrorLevel level. Logs. Used for errors that should definitely be noted.
- // Commonly used for hooks to send errors to an error tracking service.
- ErrorLevel
- // WarnLevel level. Non-critical entries that deserve eyes.
- WarnLevel
- // InfoLevel level. General operational entries about what's going on inside the
- // application.
- InfoLevel
- // DebugLevel level. Usually only enabled when debugging. Very verbose logging.
- DebugLevel
- )
- type Level uint8
- // Convert the Level to a string. E.g. PanicLevel becomes "panic".
- func (level Level) String() string {
- switch level {
- case DebugLevel:
- return "debug"
- case InfoLevel:
- return "info"
- case WarnLevel:
- return "warning"
- case ErrorLevel:
- return "error"
- case FatalLevel:
- return "fatal"
- case PanicLevel:
- return "panic"
- }
- return "unknown"
- }
- type Logger interface {
- loglib.FieldLogger
- WithConn(conn net.Conn) *loglib.Entry
- Reopen() error
- GetLogDest() string
- SetLevel(level string)
- GetLevel() string
- IsDebug() bool
- AddHook(h loglib.Hook)
- Fields(fields ...interface{}) *loglib.Entry
- }
- // Implements the Logger interface
- // It's a logrus logger wrapper that contains an instance of our LoggerHook
- type HookedLogger struct {
- // satisfy the log.FieldLogger interface
- *loglib.Logger
- h LoggerHook
- // destination, file name or "stderr", "stdout" or "off"
- dest string
- oo OutputOption
- }
- type loggerKey struct {
- dest, level string
- }
- type loggerCache map[loggerKey]Logger
- // loggers store the cached loggers created by NewLogger
- var loggers struct {
- cache loggerCache
- // mutex guards the cache
- sync.Mutex
- }
- // GetLogger returns a struct that implements Logger (i.e HookedLogger) with a custom hook.
- // It may be new or already created, (ie. singleton factory pattern)
- // The hook has been initialized with dest
- // dest can can be a path to a file, or the following string values:
- // "off" - disable any log output
- // "stdout" - write to standard output
- // "stderr" - write to standard error
- // If the file doesn't exists, a new file will be created. Otherwise it will be appended
- // Each Logger returned is cached on dest, subsequent call will get the cached logger if dest matches
- // If there was an error, the log will revert to stderr instead of using a custom hook
- func GetLogger(dest string, level string) (Logger, error) {
- loggers.Lock()
- defer loggers.Unlock()
- key := loggerKey{dest, level}
- if loggers.cache == nil {
- loggers.cache = make(loggerCache, 1)
- } else {
- if l, ok := loggers.cache[key]; ok {
- // return the one we found in the cache
- return l, nil
- }
- }
- o := parseOutputOption(dest)
- logrus, err := newLogrus(o, level)
- if err != nil {
- return nil, err
- }
- l := &HookedLogger{dest: dest}
- l.Logger = logrus
- // cache it
- loggers.cache[key] = l
- if o != OutputFile {
- return l, nil
- }
- // we'll use the hook to output instead
- logrus.Out = ioutil.Discard
- // setup the hook
- h, err := NewLogrusHook(dest)
- if err != nil {
- // revert back to stderr
- logrus.Out = os.Stderr
- return l, err
- }
- logrus.Hooks.Add(h)
- l.h = h
- return l, nil
- }
- func newLogrus(o OutputOption, level string) (*loglib.Logger, error) {
- logLevel, err := loglib.ParseLevel(level)
- if err != nil {
- return nil, err
- }
- var out io.Writer
- if o != OutputFile {
- if o == OutputNull || o == OutputStderr {
- out = os.Stderr
- } else if o == OutputStdout {
- out = os.Stdout
- } else if o == OutputOff {
- out = ioutil.Discard
- }
- } else {
- // we'll use a hook to output instead
- out = ioutil.Discard
- }
- logger := &loglib.Logger{
- Out: out,
- Formatter: new(loglib.TextFormatter),
- Hooks: make(loglib.LevelHooks),
- Level: logLevel,
- }
- return logger, nil
- }
- // AddHook adds a new logrus hook
- func (l *HookedLogger) AddHook(h loglib.Hook) {
- loglib.AddHook(h)
- }
- func (l *HookedLogger) IsDebug() bool {
- return l.GetLevel() == loglib.DebugLevel.String()
- }
- // SetLevel sets a log level, one of the LogLevels
- func (l *HookedLogger) SetLevel(level string) {
- var logLevel loglib.Level
- var err error
- if logLevel, err = loglib.ParseLevel(level); err != nil {
- return
- }
- loglib.SetLevel(logLevel)
- }
- // GetLevel gets the current log level
- func (l *HookedLogger) GetLevel() string {
- return l.Level.String()
- }
- // Reopen closes the log file and re-opens it
- func (l *HookedLogger) Reopen() error {
- if l.h == nil {
- return nil
- }
- return l.h.Reopen()
- }
- // GetLogDest Gets the file name
- func (l *HookedLogger) GetLogDest() string {
- return l.dest
- }
- // WithConn extends logrus to be able to log with a net.Conn
- func (l *HookedLogger) WithConn(conn net.Conn) *loglib.Entry {
- var addr = "unknown"
- if conn != nil {
- addr = conn.RemoteAddr().String()
- }
- return l.WithField("addr", addr)
- }
- // Fields accepts an even number of arguments in the format of ([<string> <interface{}>)1*
- func (l *HookedLogger) Fields(spec ...interface{}) *loglib.Entry {
- size := len(spec)
- if size < 2 || size%2 != 0 {
- return l.WithField("oops", "wrong fields specified")
- }
- fields := make(map[string]interface{}, size/2)
- for i := range spec {
- if i%2 != 0 {
- continue
- }
- if key, ok := spec[i].(string); ok {
- fields[key] = spec[i+1]
- } else if key, ok := spec[i].(fmt.Stringer); ok {
- fields[key.String()] = spec[i+1]
- } else {
- fields[fmt.Sprintf("%d", i)] = spec[i+1]
- }
- }
- return l.WithFields(fields)
- }
|