text_formatter_test.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. package logrus
  2. import (
  3. "bytes"
  4. "errors"
  5. "fmt"
  6. "strings"
  7. "testing"
  8. "time"
  9. "github.com/stretchr/testify/assert"
  10. )
  11. func TestFormatting(t *testing.T) {
  12. tf := &TextFormatter{DisableColors: true}
  13. testCases := []struct {
  14. value string
  15. expected string
  16. }{
  17. {`foo`, "time=\"0001-01-01T00:00:00Z\" level=panic test=foo\n"},
  18. }
  19. for _, tc := range testCases {
  20. b, _ := tf.Format(WithField("test", tc.value))
  21. if string(b) != tc.expected {
  22. t.Errorf("formatting expected for %q (result was %q instead of %q)", tc.value, string(b), tc.expected)
  23. }
  24. }
  25. }
  26. func TestQuoting(t *testing.T) {
  27. tf := &TextFormatter{DisableColors: true}
  28. checkQuoting := func(q bool, value interface{}) {
  29. b, _ := tf.Format(WithField("test", value))
  30. idx := bytes.Index(b, ([]byte)("test="))
  31. cont := bytes.Contains(b[idx+5:], []byte("\""))
  32. if cont != q {
  33. if q {
  34. t.Errorf("quoting expected for: %#v", value)
  35. } else {
  36. t.Errorf("quoting not expected for: %#v", value)
  37. }
  38. }
  39. }
  40. checkQuoting(false, "")
  41. checkQuoting(false, "abcd")
  42. checkQuoting(false, "v1.0")
  43. checkQuoting(false, "1234567890")
  44. checkQuoting(false, "/foobar")
  45. checkQuoting(false, "foo_bar")
  46. checkQuoting(false, "foo@bar")
  47. checkQuoting(false, "foobar^")
  48. checkQuoting(false, "+/-_^@f.oobar")
  49. checkQuoting(true, "foobar$")
  50. checkQuoting(true, "&foobar")
  51. checkQuoting(true, "x y")
  52. checkQuoting(true, "x,y")
  53. checkQuoting(false, errors.New("invalid"))
  54. checkQuoting(true, errors.New("invalid argument"))
  55. // Test for quoting empty fields.
  56. tf.QuoteEmptyFields = true
  57. checkQuoting(true, "")
  58. checkQuoting(false, "abcd")
  59. checkQuoting(true, errors.New("invalid argument"))
  60. }
  61. func TestEscaping(t *testing.T) {
  62. tf := &TextFormatter{DisableColors: true}
  63. testCases := []struct {
  64. value string
  65. expected string
  66. }{
  67. {`ba"r`, `ba\"r`},
  68. {`ba'r`, `ba'r`},
  69. }
  70. for _, tc := range testCases {
  71. b, _ := tf.Format(WithField("test", tc.value))
  72. if !bytes.Contains(b, []byte(tc.expected)) {
  73. t.Errorf("escaping expected for %q (result was %q instead of %q)", tc.value, string(b), tc.expected)
  74. }
  75. }
  76. }
  77. func TestEscaping_Interface(t *testing.T) {
  78. tf := &TextFormatter{DisableColors: true}
  79. ts := time.Now()
  80. testCases := []struct {
  81. value interface{}
  82. expected string
  83. }{
  84. {ts, fmt.Sprintf("\"%s\"", ts.String())},
  85. {errors.New("error: something went wrong"), "\"error: something went wrong\""},
  86. }
  87. for _, tc := range testCases {
  88. b, _ := tf.Format(WithField("test", tc.value))
  89. if !bytes.Contains(b, []byte(tc.expected)) {
  90. t.Errorf("escaping expected for %q (result was %q instead of %q)", tc.value, string(b), tc.expected)
  91. }
  92. }
  93. }
  94. func TestTimestampFormat(t *testing.T) {
  95. checkTimeStr := func(format string) {
  96. customFormatter := &TextFormatter{DisableColors: true, TimestampFormat: format}
  97. customStr, _ := customFormatter.Format(WithField("test", "test"))
  98. timeStart := bytes.Index(customStr, ([]byte)("time="))
  99. timeEnd := bytes.Index(customStr, ([]byte)("level="))
  100. timeStr := customStr[timeStart+5+len("\"") : timeEnd-1-len("\"")]
  101. if format == "" {
  102. format = time.RFC3339
  103. }
  104. _, e := time.Parse(format, (string)(timeStr))
  105. if e != nil {
  106. t.Errorf("time string \"%s\" did not match provided time format \"%s\": %s", timeStr, format, e)
  107. }
  108. }
  109. checkTimeStr("2006-01-02T15:04:05.000000000Z07:00")
  110. checkTimeStr("Mon Jan _2 15:04:05 2006")
  111. checkTimeStr("")
  112. }
  113. func TestDisableLevelTruncation(t *testing.T) {
  114. entry := &Entry{
  115. Time: time.Now(),
  116. Message: "testing",
  117. }
  118. keys := []string{}
  119. timestampFormat := "Mon Jan 2 15:04:05 -0700 MST 2006"
  120. checkDisableTruncation := func(disabled bool, level Level) {
  121. tf := &TextFormatter{DisableLevelTruncation: disabled}
  122. var b bytes.Buffer
  123. entry.Level = level
  124. tf.printColored(&b, entry, keys, timestampFormat)
  125. logLine := (&b).String()
  126. if disabled {
  127. expected := strings.ToUpper(level.String())
  128. if !strings.Contains(logLine, expected) {
  129. t.Errorf("level string expected to be %s when truncation disabled", expected)
  130. }
  131. } else {
  132. expected := strings.ToUpper(level.String())
  133. if len(level.String()) > 4 {
  134. if strings.Contains(logLine, expected) {
  135. t.Errorf("level string %s expected to be truncated to %s when truncation is enabled", expected, expected[0:4])
  136. }
  137. } else {
  138. if !strings.Contains(logLine, expected) {
  139. t.Errorf("level string expected to be %s when truncation is enabled and level string is below truncation threshold", expected)
  140. }
  141. }
  142. }
  143. }
  144. checkDisableTruncation(true, DebugLevel)
  145. checkDisableTruncation(true, InfoLevel)
  146. checkDisableTruncation(false, ErrorLevel)
  147. checkDisableTruncation(false, InfoLevel)
  148. }
  149. func TestDisableTimestampWithColoredOutput(t *testing.T) {
  150. tf := &TextFormatter{DisableTimestamp: true, ForceColors: true}
  151. b, _ := tf.Format(WithField("test", "test"))
  152. if strings.Contains(string(b), "[0000]") {
  153. t.Error("timestamp not expected when DisableTimestamp is true")
  154. }
  155. }
  156. func TestTextFormatterFieldMap(t *testing.T) {
  157. formatter := &TextFormatter{
  158. DisableColors: true,
  159. FieldMap: FieldMap{
  160. FieldKeyMsg: "message",
  161. FieldKeyLevel: "somelevel",
  162. FieldKeyTime: "timeywimey",
  163. },
  164. }
  165. entry := &Entry{
  166. Message: "oh hi",
  167. Level: WarnLevel,
  168. Time: time.Date(1981, time.February, 24, 4, 28, 3, 100, time.UTC),
  169. Data: Fields{
  170. "field1": "f1",
  171. "message": "messagefield",
  172. "somelevel": "levelfield",
  173. "timeywimey": "timeywimeyfield",
  174. },
  175. }
  176. b, err := formatter.Format(entry)
  177. if err != nil {
  178. t.Fatal("Unable to format entry: ", err)
  179. }
  180. assert.Equal(t,
  181. `timeywimey="1981-02-24T04:28:03Z" `+
  182. `somelevel=warning `+
  183. `message="oh hi" `+
  184. `field1=f1 `+
  185. `fields.message=messagefield `+
  186. `fields.somelevel=levelfield `+
  187. `fields.timeywimey=timeywimeyfield`+"\n",
  188. string(b),
  189. "Formatted output doesn't respect FieldMap")
  190. }
  191. // TODO add tests for sorting etc., this requires a parser for the text
  192. // formatter output.