log.go 4.9 KB

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