Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

text_formatter.go 8.5KB

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