text_formatter.go 7.5 KB

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