logger.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. package lgr
  2. import (
  3. "fmt"
  4. "io"
  5. "os"
  6. "runtime"
  7. "strings"
  8. "sync"
  9. "time"
  10. )
  11. var levels = []string{"DEBUG", "INFO", "WARN", "ERROR", "PANIC", "FATAL"}
  12. // Logger provided simple logger with basic support of levels. Thread safe
  13. type Logger struct {
  14. stdout, stderr io.Writer
  15. dbg bool
  16. lock sync.Mutex
  17. callerFile, callerFunc bool
  18. now nowFn
  19. fatal panicFn
  20. skipCallers int
  21. levelBraces bool
  22. }
  23. type nowFn func() time.Time
  24. type panicFn func()
  25. // New makes new leveled logger. Accepts dbg flag turing on info about the caller and allowing DEBUG messages/
  26. // Two writers can be passed optionally - first for out and second for err
  27. func New(options ...Option) *Logger {
  28. res := Logger{
  29. now: time.Now,
  30. fatal: func() { os.Exit(1) },
  31. stdout: os.Stdout,
  32. stderr: os.Stderr,
  33. skipCallers: 1,
  34. }
  35. for _, opt := range options {
  36. opt(&res)
  37. }
  38. return &res
  39. }
  40. // Logf implements L interface to output with printf style.
  41. // Each line prefixed with ts, level and optionally (dbg mode only) by caller info.
  42. // ERROR and FATAL also send the same line to err writer.
  43. // FATAL adds runtime stack and os.exit(1), like panic.
  44. func (l *Logger) Logf(format string, args ...interface{}) {
  45. lv, msg := l.extractLevel(fmt.Sprintf(format, args...))
  46. var bld strings.Builder
  47. bld.WriteString(l.now().Format("2006/01/02 15:04:05.000 "))
  48. bld.WriteString(lv)
  49. if l.dbg && (l.callerFile || l.callerFunc) {
  50. if pc, file, line, ok := runtime.Caller(l.skipCallers); ok {
  51. funcName := ""
  52. if l.callerFunc {
  53. funcNameElems := strings.Split(runtime.FuncForPC(pc).Name(), "/")
  54. funcName = funcNameElems[len(funcNameElems)-1]
  55. }
  56. fileInfo := ""
  57. if l.callerFile {
  58. fnameElems := strings.Split(file, "/")
  59. fileInfo = fmt.Sprintf("%s:%d", strings.Join(fnameElems[len(fnameElems)-2:], "/"), line)
  60. if l.callerFunc {
  61. fileInfo += " "
  62. }
  63. }
  64. srcFileInfo := fmt.Sprintf("{%s%s} ", fileInfo, funcName)
  65. bld.WriteString(srcFileInfo)
  66. }
  67. }
  68. if lv == "DEBUG " && !l.dbg {
  69. return
  70. }
  71. bld.WriteString(msg) //nolint
  72. bld.WriteString("\n") //nolint
  73. l.lock.Lock()
  74. msgb := []byte(bld.String())
  75. l.stdout.Write(msgb) //nolint
  76. switch lv {
  77. case "PANIC ", "FATAL ":
  78. l.stderr.Write(msgb) //nolint
  79. l.stderr.Write(getDump()) //nolint
  80. l.fatal()
  81. case "ERROR ":
  82. l.stderr.Write(msgb) //nolint
  83. }
  84. l.lock.Unlock()
  85. }
  86. func (l *Logger) extractLevel(line string) (level, msg string) {
  87. brace := func(b string) string {
  88. if l.levelBraces {
  89. return b
  90. }
  91. return ""
  92. }
  93. spaces := " "
  94. for _, lv := range levels {
  95. if strings.HasPrefix(line, lv) {
  96. if len(lv) == 4 {
  97. spaces = " "
  98. }
  99. return brace("[") + lv + brace("]") + spaces, line[len(lv)+1:]
  100. }
  101. if strings.HasPrefix(line, "["+lv+"]") {
  102. if len(lv) == 4 {
  103. spaces = " "
  104. }
  105. return brace("[") + lv + brace("]") + spaces, line[len(lv)+3:]
  106. }
  107. }
  108. return "", line
  109. }
  110. // getDump reads runtime stack and returns as a string
  111. func getDump() []byte {
  112. maxSize := 5 * 1024 * 1024
  113. stacktrace := make([]byte, maxSize)
  114. length := runtime.Stack(stacktrace, true)
  115. if length > maxSize {
  116. length = maxSize
  117. }
  118. return stacktrace[:length]
  119. }
  120. // Option func type
  121. type Option func(l *Logger)
  122. // Out sets out writer
  123. func Out(w io.Writer) Option {
  124. return func(l *Logger) {
  125. l.stdout = w
  126. }
  127. }
  128. // Err sets error writer
  129. func Err(w io.Writer) Option {
  130. return func(l *Logger) {
  131. l.stderr = w
  132. }
  133. }
  134. // Debug turn on dbg mode
  135. func Debug(l *Logger) {
  136. l.dbg = true
  137. }
  138. // CallerFile adds caller info with file, and line number
  139. func CallerFile(l *Logger) {
  140. l.callerFile = true
  141. }
  142. // CallerFunc adds caller info with function name
  143. func CallerFunc(l *Logger) {
  144. l.callerFunc = true
  145. }
  146. // LevelBraces adds [] to level
  147. func LevelBraces(l *Logger) {
  148. l.levelBraces = true
  149. }