formatter.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package log
  15. import (
  16. "bytes"
  17. "fmt"
  18. "io"
  19. "os"
  20. "regexp"
  21. "runtime"
  22. "sort"
  23. "strings"
  24. "time"
  25. "github.com/mgutz/ansi"
  26. "github.com/sirupsen/logrus"
  27. "golang.org/x/crypto/ssh/terminal"
  28. )
  29. const reset = ansi.Reset
  30. var (
  31. baseTimestamp time.Time
  32. )
  33. func init() {
  34. baseTimestamp = time.Now()
  35. }
  36. func miniTS() int {
  37. return int(time.Since(baseTimestamp) / time.Second)
  38. }
  39. type TextFormatter struct {
  40. // Set to true to bypass checking for a TTY before outputting colors
  41. ForceColors bool
  42. // Force disabling colors
  43. DisableColors bool
  44. // Disable timestamp logging, useful when output is redirected to logging
  45. // system that already adds timestamps
  46. DisableTimestamp bool
  47. // Enable logging of just the time passed since beginning of execution.
  48. ShortTimestamp bool
  49. // Timestamp format to use for display when a full timestamp is printed.
  50. TimestampFormat string
  51. TimeZone string
  52. // The fields are sorted by default for a consistent output. For applications
  53. // that log extremely frequently and don't use the JSON formatter this may not
  54. // be desired.
  55. DisableSorting bool
  56. // Pad msg field with spaces on the right for display.
  57. // The value for this parameter will be the size of padding.
  58. // Its default value is zero, which means no padding will be applied for msg.
  59. SpacePadding int
  60. }
  61. func (f *TextFormatter) Format(entry *logrus.Entry) ([]byte, error) {
  62. var keys []string = make([]string, 0, len(entry.Data))
  63. for k := range entry.Data {
  64. if k != "prefix" && k != "caller" {
  65. keys = append(keys, k)
  66. }
  67. }
  68. if !f.DisableSorting {
  69. sort.Strings(keys)
  70. }
  71. b := &bytes.Buffer{}
  72. prefixFieldClashes(entry.Data)
  73. checkIfTerminal := func(w io.Writer) bool {
  74. switch v := w.(type) {
  75. case *os.File:
  76. return terminal.IsTerminal(int(v.Fd()))
  77. default:
  78. return false
  79. }
  80. }
  81. isColorTerminal := checkIfTerminal(entry.Logger.Out) && (runtime.GOOS != "windows")
  82. isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors
  83. timestampFormat := f.TimestampFormat
  84. if timestampFormat == "" {
  85. timestampFormat = time.Stamp
  86. }
  87. if isColored {
  88. f.printColored(b, entry, keys, timestampFormat)
  89. } else {
  90. f.printNoColored(b, entry, keys, timestampFormat)
  91. }
  92. b.WriteByte('\n')
  93. return b.Bytes(), nil
  94. }
  95. func (f *TextFormatter) printColored(b *bytes.Buffer, entry *logrus.Entry, keys []string, timestampFormat string) {
  96. var levelColor string
  97. var levelText string
  98. switch entry.Level {
  99. case logrus.InfoLevel:
  100. levelColor = ansi.Green
  101. case logrus.WarnLevel:
  102. levelColor = ansi.Yellow
  103. case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel:
  104. levelColor = ansi.Red
  105. default:
  106. levelColor = ansi.Blue
  107. }
  108. if entry.Level != logrus.WarnLevel {
  109. levelText = strings.ToUpper(entry.Level.String())
  110. } else {
  111. levelText = "WARN"
  112. }
  113. prefix := " "
  114. message := entry.Message
  115. if prefixValue, ok := entry.Data["prefix"]; ok {
  116. prefix = fmt.Sprint(ansi.Cyan, prefixValue, ":", reset, " ")
  117. } else {
  118. prefixValue, trimmedMsg := extractPrefix(entry.Message)
  119. if len(prefixValue) > 0 {
  120. prefix = fmt.Sprint(ansi.Cyan, prefixValue, ":", reset, " ")
  121. message = trimmedMsg
  122. }
  123. }
  124. caller, _ := entry.Data["caller"]
  125. messageFormat := "%s"
  126. if f.SpacePadding != 0 {
  127. messageFormat = fmt.Sprintf("%%-%ds", f.SpacePadding)
  128. }
  129. if f.ShortTimestamp {
  130. fmt.Fprintf(b, "%s[%s %04d %s]%s%s"+messageFormat, levelColor, levelText[:1], miniTS(), caller, reset, prefix, message)
  131. } else {
  132. fmt.Fprintf(b, "%s[%s %s %s]%s%s"+messageFormat, levelColor, levelText[:1], entry.Time.Format(timestampFormat), caller, reset, prefix, message)
  133. }
  134. for _, k := range keys {
  135. v := entry.Data[k]
  136. fmt.Fprintf(b, " %s%s%s=%+v", levelColor, k, reset, v)
  137. }
  138. }
  139. func (f *TextFormatter) printNoColored(b *bytes.Buffer, entry *logrus.Entry, keys []string, timestampFormat string) {
  140. levelText := entry.Level.String()
  141. prefix := " "
  142. message := entry.Message
  143. if prefixValue, ok := entry.Data["prefix"]; ok {
  144. prefix = fmt.Sprint(prefixValue, ":", " ")
  145. } else {
  146. prefixValue, trimmedMsg := extractPrefix(entry.Message)
  147. if len(prefixValue) > 0 {
  148. prefix = fmt.Sprint(prefixValue, ":", " ")
  149. message = trimmedMsg
  150. }
  151. }
  152. caller, _ := entry.Data["caller"]
  153. messageFormat := "%s"
  154. if f.SpacePadding != 0 {
  155. messageFormat = fmt.Sprintf("%%-%ds", f.SpacePadding)
  156. }
  157. if f.ShortTimestamp {
  158. fmt.Fprintf(b, "[%s %04d %s]%s"+messageFormat, levelText, miniTS(), caller, prefix, message)
  159. } else {
  160. var tz *time.Location
  161. if len(f.TimeZone) > 0 {
  162. tz, _ = time.LoadLocation(f.TimeZone)
  163. }
  164. if tz == nil {
  165. tz = time.Local
  166. }
  167. fmt.Fprintf(b, "[%s %s %s]%s"+messageFormat, levelText, entry.Time.In(tz).Format(timestampFormat), caller, prefix, message)
  168. }
  169. for _, k := range keys {
  170. v := entry.Data[k]
  171. fmt.Fprintf(b, " %s=%+v", k, v)
  172. }
  173. }
  174. func needsQuoting(text string) bool {
  175. for _, ch := range text {
  176. if !((ch >= 'a' && ch <= 'z') ||
  177. (ch >= 'A' && ch <= 'Z') ||
  178. (ch >= '0' && ch <= '9') ||
  179. ch == '-' || ch == '.') {
  180. return false
  181. }
  182. }
  183. return true
  184. }
  185. func extractPrefix(msg string) (string, string) {
  186. prefix := ""
  187. regex := regexp.MustCompile("^\\[(.*?)\\]")
  188. if regex.MatchString(msg) {
  189. match := regex.FindString(msg)
  190. prefix, msg = match[1:len(match)-1], strings.TrimSpace(msg[len(match):])
  191. }
  192. return prefix, msg
  193. }
  194. func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
  195. b.WriteString(key)
  196. b.WriteByte('=')
  197. switch value := value.(type) {
  198. case string:
  199. if needsQuoting(value) {
  200. b.WriteString(value)
  201. } else {
  202. fmt.Fprintf(b, "%q", value)
  203. }
  204. case error:
  205. errmsg := value.Error()
  206. if needsQuoting(errmsg) {
  207. b.WriteString(errmsg)
  208. } else {
  209. fmt.Fprintf(b, "%q", value)
  210. }
  211. default:
  212. fmt.Fprint(b, value)
  213. }
  214. b.WriteByte(' ')
  215. }
  216. func prefixFieldClashes(data logrus.Fields) {
  217. _, ok := data["time"]
  218. if ok {
  219. data["fields.time"] = data["time"]
  220. }
  221. _, ok = data["msg"]
  222. if ok {
  223. data["fields.msg"] = data["msg"]
  224. }
  225. _, ok = data["level"]
  226. if ok {
  227. data["fields.level"] = data["level"]
  228. }
  229. }