2
0

log.go 5.6 KB

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