text_formatter.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. package logrus
  2. import (
  3. "bytes"
  4. "fmt"
  5. "os"
  6. "sort"
  7. "strings"
  8. "sync"
  9. "time"
  10. )
  11. const (
  12. nocolor = 0
  13. red = 31
  14. green = 32
  15. yellow = 33
  16. blue = 36
  17. gray = 37
  18. )
  19. var (
  20. baseTimestamp time.Time
  21. emptyFieldMap FieldMap
  22. )
  23. func init() {
  24. baseTimestamp = time.Now()
  25. }
  26. // TextFormatter formats logs into text
  27. type TextFormatter struct {
  28. // Set to true to bypass checking for a TTY before outputting colors.
  29. ForceColors bool
  30. // Force disabling colors.
  31. DisableColors bool
  32. // Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/
  33. EnvironmentOverrideColors bool
  34. // Disable timestamp logging. useful when output is redirected to logging
  35. // system that already adds timestamps.
  36. DisableTimestamp bool
  37. // Enable logging the full timestamp when a TTY is attached instead of just
  38. // the time passed since beginning of execution.
  39. FullTimestamp bool
  40. // TimestampFormat to use for display when a full timestamp is printed
  41. TimestampFormat string
  42. // The fields are sorted by default for a consistent output. For applications
  43. // that log extremely frequently and don't use the JSON formatter this may not
  44. // be desired.
  45. DisableSorting bool
  46. // The keys sorting function, when uninitialized it uses sort.Strings.
  47. SortingFunc func([]string)
  48. // Disables the truncation of the level text to 4 characters.
  49. DisableLevelTruncation bool
  50. // QuoteEmptyFields will wrap empty fields in quotes if true
  51. QuoteEmptyFields bool
  52. // Whether the logger's out is to a terminal
  53. isTerminal bool
  54. // FieldMap allows users to customize the names of keys for default fields.
  55. // As an example:
  56. // formatter := &TextFormatter{
  57. // FieldMap: FieldMap{
  58. // FieldKeyTime: "@timestamp",
  59. // FieldKeyLevel: "@level",
  60. // FieldKeyMsg: "@message"}}
  61. FieldMap FieldMap
  62. terminalInitOnce sync.Once
  63. }
  64. func (f *TextFormatter) init(entry *Entry) {
  65. if entry.Logger != nil {
  66. f.isTerminal = checkIfTerminal(entry.Logger.Out)
  67. if f.isTerminal {
  68. initTerminal(entry.Logger.Out)
  69. }
  70. }
  71. }
  72. func (f *TextFormatter) isColored() bool {
  73. isColored := f.ForceColors || f.isTerminal
  74. if f.EnvironmentOverrideColors {
  75. if force, ok := os.LookupEnv("CLICOLOR_FORCE"); ok && force != "0" {
  76. isColored = true
  77. } else if ok && force == "0" {
  78. isColored = false
  79. } else if os.Getenv("CLICOLOR") == "0" {
  80. isColored = false
  81. }
  82. }
  83. return isColored && !f.DisableColors
  84. }
  85. // Format renders a single log entry
  86. func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
  87. prefixFieldClashes(entry.Data, f.FieldMap, entry.HasCaller())
  88. keys := make([]string, 0, len(entry.Data))
  89. for k := range entry.Data {
  90. keys = append(keys, k)
  91. }
  92. fixedKeys := make([]string, 0, 4+len(entry.Data))
  93. if !f.DisableTimestamp {
  94. fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime))
  95. }
  96. fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLevel))
  97. if entry.Message != "" {
  98. fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyMsg))
  99. }
  100. if entry.err != "" {
  101. fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError))
  102. }
  103. if entry.HasCaller() {
  104. fixedKeys = append(fixedKeys,
  105. f.FieldMap.resolve(FieldKeyFunc), f.FieldMap.resolve(FieldKeyFile))
  106. }
  107. if !f.DisableSorting {
  108. if f.SortingFunc == nil {
  109. sort.Strings(keys)
  110. fixedKeys = append(fixedKeys, keys...)
  111. } else {
  112. if !f.isColored() {
  113. fixedKeys = append(fixedKeys, keys...)
  114. f.SortingFunc(fixedKeys)
  115. } else {
  116. f.SortingFunc(keys)
  117. }
  118. }
  119. } else {
  120. fixedKeys = append(fixedKeys, keys...)
  121. }
  122. var b *bytes.Buffer
  123. if entry.Buffer != nil {
  124. b = entry.Buffer
  125. } else {
  126. b = &bytes.Buffer{}
  127. }
  128. f.terminalInitOnce.Do(func() { f.init(entry) })
  129. timestampFormat := f.TimestampFormat
  130. if timestampFormat == "" {
  131. timestampFormat = defaultTimestampFormat
  132. }
  133. if f.isColored() {
  134. f.printColored(b, entry, keys, timestampFormat)
  135. } else {
  136. for _, key := range fixedKeys {
  137. var value interface{}
  138. switch {
  139. case key == f.FieldMap.resolve(FieldKeyTime):
  140. value = entry.Time.Format(timestampFormat)
  141. case key == f.FieldMap.resolve(FieldKeyLevel):
  142. value = entry.Level.String()
  143. case key == f.FieldMap.resolve(FieldKeyMsg):
  144. value = entry.Message
  145. case key == f.FieldMap.resolve(FieldKeyLogrusError):
  146. value = entry.err
  147. case key == f.FieldMap.resolve(FieldKeyFunc) && entry.HasCaller():
  148. value = entry.Caller.Function
  149. case key == f.FieldMap.resolve(FieldKeyFile) && entry.HasCaller():
  150. value = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
  151. default:
  152. value = entry.Data[key]
  153. }
  154. f.appendKeyValue(b, key, value)
  155. }
  156. }
  157. b.WriteByte('\n')
  158. return b.Bytes(), nil
  159. }
  160. func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) {
  161. var levelColor int
  162. switch entry.Level {
  163. case DebugLevel, TraceLevel:
  164. levelColor = gray
  165. case WarnLevel:
  166. levelColor = yellow
  167. case ErrorLevel, FatalLevel, PanicLevel:
  168. levelColor = red
  169. default:
  170. levelColor = blue
  171. }
  172. levelText := strings.ToUpper(entry.Level.String())
  173. if !f.DisableLevelTruncation {
  174. levelText = levelText[0:4]
  175. }
  176. // Remove a single newline if it already exists in the message to keep
  177. // the behavior of logrus text_formatter the same as the stdlib log package
  178. entry.Message = strings.TrimSuffix(entry.Message, "\n")
  179. caller := ""
  180. if entry.HasCaller() {
  181. caller = fmt.Sprintf("%s:%d %s()",
  182. entry.Caller.File, entry.Caller.Line, entry.Caller.Function)
  183. }
  184. if f.DisableTimestamp {
  185. fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message)
  186. } else if !f.FullTimestamp {
  187. fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message)
  188. } else {
  189. fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), caller, entry.Message)
  190. }
  191. for _, k := range keys {
  192. v := entry.Data[k]
  193. fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
  194. f.appendValue(b, v)
  195. }
  196. }
  197. func (f *TextFormatter) needsQuoting(text string) bool {
  198. if f.QuoteEmptyFields && len(text) == 0 {
  199. return true
  200. }
  201. for _, ch := range text {
  202. if !((ch >= 'a' && ch <= 'z') ||
  203. (ch >= 'A' && ch <= 'Z') ||
  204. (ch >= '0' && ch <= '9') ||
  205. ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
  206. return true
  207. }
  208. }
  209. return false
  210. }
  211. func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
  212. if b.Len() > 0 {
  213. b.WriteByte(' ')
  214. }
  215. b.WriteString(key)
  216. b.WriteByte('=')
  217. f.appendValue(b, value)
  218. }
  219. func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
  220. stringVal, ok := value.(string)
  221. if !ok {
  222. stringVal = fmt.Sprint(value)
  223. }
  224. if !f.needsQuoting(stringVal) {
  225. b.WriteString(stringVal)
  226. } else {
  227. b.WriteString(fmt.Sprintf("%q", stringVal))
  228. }
  229. }