1package log 2 3import ( 4 log "github.com/sirupsen/logrus" 5 "io" 6 "io/ioutil" 7 "net" 8 "os" 9 "sync" 10) 11 12// The following are taken from logrus 13const ( 14 // PanicLevel level, highest level of severity. Logs and then calls panic with the 15 // message passed to Debug, Info, ... 16 PanicLevel Level = iota 17 // FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the 18 // logging level is set to Panic. 19 FatalLevel 20 // ErrorLevel level. Logs. Used for errors that should definitely be noted. 21 // Commonly used for hooks to send errors to an error tracking service. 22 ErrorLevel 23 // WarnLevel level. Non-critical entries that deserve eyes. 24 WarnLevel 25 // InfoLevel level. General operational entries about what's going on inside the 26 // application. 27 InfoLevel 28 // DebugLevel level. Usually only enabled when debugging. Very verbose logging. 29 DebugLevel 30) 31 32type Level uint8 33 34// Convert the Level to a string. E.g. PanicLevel becomes "panic". 35func (level Level) String() string { 36 switch level { 37 case DebugLevel: 38 return "debug" 39 case InfoLevel: 40 return "info" 41 case WarnLevel: 42 return "warning" 43 case ErrorLevel: 44 return "error" 45 case FatalLevel: 46 return "fatal" 47 case PanicLevel: 48 return "panic" 49 } 50 51 return "unknown" 52} 53 54type Logger interface { 55 log.FieldLogger 56 WithConn(conn net.Conn) *log.Entry 57 Reopen() error 58 GetLogDest() string 59 SetLevel(level string) 60 GetLevel() string 61 IsDebug() bool 62 AddHook(h log.Hook) 63} 64 65// Implements the Logger interface 66// It's a logrus logger wrapper that contains an instance of our LoggerHook 67type HookedLogger struct { 68 69 // satisfy the log.FieldLogger interface 70 *log.Logger 71 72 h LoggerHook 73 74 // destination, file name or "stderr", "stdout" or "off" 75 dest string 76 77 oo OutputOption 78} 79 80type loggerKey struct { 81 dest, level string 82} 83 84type loggerCache map[loggerKey]Logger 85 86// loggers store the cached loggers created by NewLogger 87var loggers struct { 88 cache loggerCache 89 // mutex guards the cache 90 sync.Mutex 91} 92 93// GetLogger returns a struct that implements Logger (i.e HookedLogger) with a custom hook. 94// It may be new or already created, (ie. singleton factory pattern) 95// The hook has been initialized with dest 96// dest can can be a path to a file, or the following string values: 97// "off" - disable any log output 98// "stdout" - write to standard output 99// "stderr" - write to standard error 100// If the file doesn't exists, a new file will be created. Otherwise it will be appended 101// Each Logger returned is cached on dest, subsequent call will get the cached logger if dest matches 102// If there was an error, the log will revert to stderr instead of using a custom hook 103 104func GetLogger(dest string, level string) (Logger, error) { 105 loggers.Lock() 106 defer loggers.Unlock() 107 key := loggerKey{dest, level} 108 if loggers.cache == nil { 109 loggers.cache = make(loggerCache, 1) 110 } else { 111 if l, ok := loggers.cache[key]; ok { 112 // return the one we found in the cache 113 return l, nil 114 } 115 } 116 o := parseOutputOption(dest) 117 logrus, err := newLogrus(o, level) 118 if err != nil { 119 return nil, err 120 } 121 l := &HookedLogger{dest: dest} 122 l.Logger = logrus 123 124 // cache it 125 loggers.cache[key] = l 126 127 if o != OutputFile { 128 return l, nil 129 } 130 // we'll use the hook to output instead 131 logrus.Out = ioutil.Discard 132 // setup the hook 133 if h, err := NewLogrusHook(dest); err != nil { 134 // revert back to stderr 135 logrus.Out = os.Stderr 136 return l, err 137 } else { 138 logrus.Hooks.Add(h) 139 l.h = h 140 } 141 142 return l, nil 143 144} 145 146func newLogrus(o OutputOption, level string) (*log.Logger, error) { 147 logLevel, err := log.ParseLevel(level) 148 if err != nil { 149 return nil, err 150 } 151 var out io.Writer 152 153 if o != OutputFile { 154 if o == OutputNull || o == OutputStderr { 155 out = os.Stderr 156 } else if o == OutputStdout { 157 out = os.Stdout 158 } else if o == OutputOff { 159 out = ioutil.Discard 160 } 161 } else { 162 // we'll use a hook to output instead 163 out = ioutil.Discard 164 } 165 166 logger := &log.Logger{ 167 Out: out, 168 Formatter: new(log.TextFormatter), 169 Hooks: make(log.LevelHooks), 170 Level: logLevel, 171 } 172 173 return logger, nil 174} 175 176// AddHook adds a new logrus hook 177func (l *HookedLogger) AddHook(h log.Hook) { 178 log.AddHook(h) 179} 180 181func (l *HookedLogger) IsDebug() bool { 182 return l.GetLevel() == log.DebugLevel.String() 183} 184 185// SetLevel sets a log level, one of the LogLevels 186func (l *HookedLogger) SetLevel(level string) { 187 var logLevel log.Level 188 var err error 189 if logLevel, err = log.ParseLevel(level); err != nil { 190 return 191 } 192 log.SetLevel(logLevel) 193} 194 195// GetLevel gets the current log level 196func (l *HookedLogger) GetLevel() string { 197 return l.Level.String() 198} 199 200// Reopen closes the log file and re-opens it 201func (l *HookedLogger) Reopen() error { 202 if l.h == nil { 203 return nil 204 } 205 return l.h.Reopen() 206} 207 208// GetLogDest Gets the file name 209func (l *HookedLogger) GetLogDest() string { 210 return l.dest 211} 212 213// WithConn extends logrus to be able to log with a net.Conn 214func (l *HookedLogger) WithConn(conn net.Conn) *log.Entry { 215 var addr = "unknown" 216 if conn != nil { 217 addr = conn.RemoteAddr().String() 218 } 219 return l.WithField("addr", addr) 220} 221