log.go 5.3 KB

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