log.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  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. type Logger interface {
  49. log.FieldLogger
  50. WithConn(conn net.Conn) *log.Entry
  51. Reopen() error
  52. GetLogDest() string
  53. SetLevel(level string)
  54. GetLevel() string
  55. IsDebug() bool
  56. AddHook(h log.Hook)
  57. }
  58. // Implements the Logger interface
  59. // It's a logrus logger wrapper that contains an instance of our LoggerHook
  60. type HookedLogger struct {
  61. // satisfy the log.FieldLogger interface
  62. *log.Logger
  63. h LoggerHook
  64. // destination, file name or "stderr", "stdout" or "off"
  65. dest string
  66. oo OutputOption
  67. addHooks map[log.Hook]bool
  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. // GetLogger returns a struct that implements Logger (i.e HookedLogger) with a custom hook.
  80. // It may be new or already created, (ie. singleton factory pattern)
  81. // The hook has been initialized with dest
  82. // dest can can be a path to a file, or the following string values:
  83. // "off" - disable any log output
  84. // "stdout" - write to standard output
  85. // "stderr" - write to standard error
  86. // If the file doesn't exists, a new file will be created. Otherwise it will be appended
  87. // Each Logger returned is cached on dest, subsequent call will get the cached logger if dest matches
  88. // If there was an error, the log will revert to stderr instead of using a custom hook
  89. func GetLogger(dest string, level string) (Logger, error) {
  90. loggers.Lock()
  91. defer loggers.Unlock()
  92. key := loggerKey{dest, level}
  93. if loggers.cache == nil {
  94. loggers.cache = make(loggerCache, 1)
  95. } else {
  96. if l, ok := loggers.cache[key]; ok {
  97. // return the one we found in the cache
  98. return l, nil
  99. }
  100. }
  101. o := parseOutputOption(dest)
  102. logrus, err := newLogrus(o, level)
  103. if err != nil {
  104. return nil, err
  105. }
  106. l := &HookedLogger{dest: dest}
  107. l.Logger = logrus
  108. // cache it
  109. loggers.cache[key] = l
  110. if o != OutputFile {
  111. return l, nil
  112. }
  113. // we'll use the hook to output instead
  114. logrus.Out = ioutil.Discard
  115. // setup the hook
  116. if h, err := NewLogrusHook(dest); err != nil {
  117. // revert back to stderr
  118. logrus.Out = os.Stderr
  119. return l, err
  120. } else {
  121. logrus.Hooks.Add(h)
  122. l.h = h
  123. }
  124. return l, nil
  125. }
  126. func newLogrus(o OutputOption, level string) (*log.Logger, error) {
  127. logLevel, err := log.ParseLevel(level)
  128. if err != nil {
  129. return nil, err
  130. }
  131. var out io.Writer
  132. if o != OutputFile {
  133. if o == OutputNull || o == OutputStderr {
  134. out = os.Stderr
  135. } else if o == OutputStdout {
  136. out = os.Stdout
  137. } else if o == OutputOff {
  138. out = ioutil.Discard
  139. }
  140. } else {
  141. // we'll use a hook to output instead
  142. out = ioutil.Discard
  143. }
  144. logger := &log.Logger{
  145. Out: out,
  146. Formatter: new(log.TextFormatter),
  147. Hooks: make(log.LevelHooks),
  148. Level: logLevel,
  149. }
  150. return logger, nil
  151. }
  152. // AddHook adds a new logrus hook
  153. // ensures same hook can't be added more than once
  154. func (l *HookedLogger) AddHook(h log.Hook) {
  155. if l.addHooks == nil {
  156. l.addHooks = make(map[log.Hook]bool, 0)
  157. }
  158. if ok := l.addHooks[h]; ok {
  159. return
  160. }
  161. l.Hooks.Add(h)
  162. l.addHooks[h] = true
  163. }
  164. func (l *HookedLogger) IsDebug() bool {
  165. return l.GetLevel() == log.DebugLevel.String()
  166. }
  167. // SetLevel sets a log level, one of the LogLevels
  168. func (l *HookedLogger) SetLevel(level string) {
  169. var logLevel log.Level
  170. var err error
  171. if logLevel, err = log.ParseLevel(level); err != nil {
  172. return
  173. }
  174. log.SetLevel(logLevel)
  175. }
  176. // GetLevel gets the current log level
  177. func (l *HookedLogger) GetLevel() string {
  178. return l.Level.String()
  179. }
  180. // Reopen closes the log file and re-opens it
  181. func (l *HookedLogger) Reopen() error {
  182. if l.h == nil {
  183. return nil
  184. }
  185. return l.h.Reopen()
  186. }
  187. // GetLogDest Gets the file name
  188. func (l *HookedLogger) GetLogDest() string {
  189. return l.dest
  190. }
  191. // WithConn extends logrus to be able to log with a net.Conn
  192. func (l *HookedLogger) WithConn(conn net.Conn) *log.Entry {
  193. var addr = "unknown"
  194. if conn != nil {
  195. addr = conn.RemoteAddr().String()
  196. }
  197. return l.WithField("addr", addr)
  198. }